Skip to content

Instantly share code, notes, and snippets.

@daviddahl
Created June 17, 2010 17:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daviddahl/442447 to your computer and use it in GitHub Desktop.
Save daviddahl/442447 to your computer and use it in GitHub Desktop.
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is DevTools (HeadsUpDisplay) Console Code
*
* The Initial Developer of the Original Code is
* Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Dahl <ddahl@mozilla.com> (original author)
* Rob Campbell <rcampbell@mozilla.com>
* Johnathan Nightingale <jnightingale@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = ["HUDService"];
XPCOMUtils.defineLazyServiceGetter(this, "scriptError",
"@mozilla.org/scripterror;1",
"nsIScriptError");
XPCOMUtils.defineLazyServiceGetter(this, "activityDistributor",
"@mozilla.org/network/http-activity-distributor;1",
"nsIHttpActivityDistributor");
XPCOMUtils.defineLazyServiceGetter(this, "sss",
"@mozilla.org/content/style-sheet-service;1",
"nsIStyleSheetService");
let log = LogFactory("*** HUDService:");
const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
const ELEMENT_NS = "html:";
const HUD_STYLESHEET_URI = "chrome://global/content/headsUpDisplay.css";
const HUD_STRINGS_URI = "chrome://global/locale/headsUpDisplay.properties";
const ERRORS = { LOG_MESSAGE_MISSING_ARGS:
"Missing arguments: aMessage, aConsoleNode and aMessageNode are required.",
CANNOT_GET_HUD: "Cannot getHeads Up Display with provided ID",
MISSING_ARGS: "Missing arguments",
LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode",
};
function HUD_SERVICE()
{
// TODO: provide mixins for FENNEC: bug 568621
if (this.application == "FIREFOX") {
var mixins = new FirefoxApplicationHooks();
}
else {
throw new Error("Unsupported Application");
}
this.mixins = mixins;
this.storage = new ConsoleStorage();
this.defaultFilterPrefs = this.storage.defaultDisplayPrefs;
this.defaultGlobalConsolePrefs = this.storage.defaultGlobalConsolePrefs;
this.stringBundle = Services.strings.createBundle(HUD_STRINGS_URI);
// load stylesheet with StyleSheetService
var uri = Services.io.newURI(HUD_STYLESHEET_URI, null, null);
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
// begin observing HTTP traffic
this.startHTTPObservation();
};
HUD_SERVICE.prototype =
{
/**
* L10N shortcut function
*
* @param string aName
* @returns string
*/
getStr: function HS_getStr(aName)
{
return this.stringBundle.GetStringFromName(aName);
},
/**
* L10N shortcut function
*
* @param string aName
* @returns (format) string
*/
getFormatStr: function HS_getFormatStr(aName, aArray)
{
return this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
},
/**
* getter for UI commands to be used by the frontend
*
* @returns object
*/
get consoleUI() {
return HeadsUpDisplayUICommands;
},
/**
* The Gecko Application's name
*
* @return string
*/
get application() { return appName(); },
/**
* Collection of HUDIds that map to the tabs/windows/contexts
* that a HeadsUpDisplay can be activated for.
*/
activatedContexts: [],
/**
* Registry of HeadsUpDisplay DOM node ids
*/
_headsUpDisplays: {},
/**
* Mapping of HUDIds to URIspecs
*/
displayRegistry: {},
/**
* Mapping of URISpecs to HUDIds
*/
uriRegistry: {},
/**
* The nsILoadGroups being tracked
*/
loadGroups: {},
/**
* The sequencer is a generator (after initialization) that returns unique
* integers
*/
sequencer: null,
/**
* Each HeadsUpDisplay has a set of filter preferences
*/
filterPrefs: {},
/**
* We keep track of all of the DOMMutation event listers in this object
*/
mutationEventFunctions: {},
/**
* Event handler to get window errors
* XXX: a bit of a hack but is able to associate
* errors thrown in a window's scope we do not know
* about because of the nsIConsoleMessages not having a
* window reference.
* see bug 567165
*
* @param nsIDOMWindow aWindow
* @returns boolean
*/
setOnErrorHandler: function HS_setOnErrorHandler(aWindow) {
var self = this;
var window = aWindow.wrappedJSObject;
var console = window.console;
var origOnerrorFunc = window.onerror;
window.onerror = function windowOnError(aErrorMsg, aURL, aLineNumber)
{
var lineNum = "";
if (aLineNumber) {
lineNum = self.getFormatStr("errLine", [aLineNumber]);
}
console.error(aErrorMsg + " @ " + aURL + " " + lineNum);
if (origOnerrorFunc) {
origOnerrorFunc(aErrorMsg, aURL, aLineNumber);
}
return false;
};
},
/**
* Tell the HUDService that a HeadsUpDisplay can be activated
* for the window or context that has 'aContextDOMId' node id
*
* @param string aContextDOMId
* @return void
*/
registerActiveContext: function HS_registerActiveContext(aContextDOMId)
{
this.activatedContexts.push(aContextDOMId);
},
/**
* Firefox-specific current tab getter
*
* @returns nsIDOMWindow
*/
currentContext: function HS_currentContext() {
return this.mixins.getCurrentContext();
},
/**
* Tell the HUDService that a HeadsUpDisplay should be deactivated
*
* @param string aContextDOMId
* @return void
*/
unregisterActiveContext: function HS_deregisterActiveContext(aContextDOMId)
{
var domId = aContextDOMId.split("_")[1];
var idx = this.activatedContexts.indexOf(domId);
if (idx > -1) {
this.activatedContexts.splice(idx, 1);
}
},
/**
* Tells callers that a HeadsUpDisplay can be activated for the context
*
* @param string aContextDOMId
* @return boolean
*/
canActivateContext: function HS_registerActiveContext(aContextDOMId)
{
var domId = aContextDOMId.split("_")[1];
for (var idx in this.activatedContexts) {
if ((this.activatedContexts[idx]) == domId){
return true;
}
}
return false;
},
/**
* Activate a HeadsUpDisplay for the current window
*
* @param nsIDOMWindow aContext
* @returns void
*/
activateHUDForContext: function HS_activateHUDForContext(aContext)
{
var window = aContext.linkedBrowser.contentWindow;
var id = aContext.linkedBrowser.parentNode.getAttribute("id");
this.registerActiveContext(id);
new Initializer(window);
},
/**
* Deactivate a HeadsUpDisplay for the current window
*
* @param nsIDOMWindow aContext
* @returns void
*/
deactivateHUDForContext: function HS_activateHUDForContext(aContext)
{
var gBrowser = HUDService.currentContext().gBrowser;
var window = aContext.linkedBrowser.contentWindow;
var browser = gBrowser.getBrowserForDocument(window.top.document);
var tabId = gBrowser.getNotificationBox(browser).getAttribute("id");
var hudId = "hud_" + tabId;
var displayNode = this.getHeadsUpDisplay(hudId);
this.unregisterActiveContext(hudId);
this.unregisterDisplay(hudId);
window.wrappedJSObject.console = null;
},
/**
* Clear the specified HeadsUpDisplay
*
* @param string aId
* @returns void
*/
clearDisplay: function HS_clearDisplay(aId)
{
var displayNode = this.getOutputNodeById(aId);
var outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
while (outputNode.firstChild) {
outputNode.removeChild(outputNode.firstChild);
}
},
/**
* 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();
},
/**
* get the default filter prefs
*
* @param string aHUDId
* @returns JS Object
*/
getDefaultFilterPrefs: function HS_getDefaultFilterPrefs(aHUDId) {
return this.filterPrefs[aHUDId];
},
/**
* get the current filter prefs
*
* @param string aHUDId
* @returns JS Object
*/
getFilterPrefs: function HS_getFilterPrefs(aHUDId) {
return this.filterPrefs[aHUDId];
},
/**
* get the filter state for a specfic toggle button on a heads up display
*
* @param string aHUDId
* @param string aToggleType
* @returns boolean
*/
getFilterState: function HS_getFilterState(aHUDId, aToggleType)
{
if (!aHUDId) {
return false;
}
try {
var bool = this.filterPrefs[aHUDId][aToggleType];
return bool;
}
catch (ex) {
return false;
}
},
/**
* set the filter state for a specific toggle button on a heads up display
*
* @param string aHUDId
* @param string aToggleType
* @param boolean aState
* @returns void
*/
setFilterState: function HS_setFilterState(aHUDId, aToggleType, aState)
{
this.filterPrefs[aHUDId][aToggleType] = aState;
this.toggleBinaryFilter(aToggleType, aHUDId);
},
/**
* toggle a binary filter on the filter toolbar
*
* @param string aFilter
* @param string aHUDId
* @returns void
*/
toggleBinaryFilter:
function HS_toggleBinaryFilter(aFilter, aHUDId)
{
// this is used when document-specific listeners have to be
// started or stopped
if (aFilter == "mutation") {
this.toggleMutationListeners(aHUDId);
}
},
/**
* Register a new Heads Up Display
*
* @param string aHUDId
* @param string aURISpec
* @returns void
*/
registerDisplay: function HS_registerDisplay(aHUDId, aURISpec)
{
// register a display DOM node Id and HUD uriSpec with the service
if (!aHUDId || !aURISpec){
throw new Error(ERRORS.MISSING_ARGS);
}
this.filterPrefs[aHUDId] = this.defaultFilterPrefs;
this.displayRegistry[aHUDId] = aURISpec;
this._headsUpDisplays[aHUDId] = { id: aHUDId, };
this.registerActiveContext(aHUDId);
// init storage objects:
this.storage.createDisplay(aHUDId);
var huds = this.uriRegistry[aURISpec];
var foundHUDId = false;
if (huds) {
var len = huds.length;
for (var i = 0; i < len; i++) {
if (huds[i] == aHUDId) {
foundHUDId = true;
break;
}
}
if (!foundHUDId) {
this.uriRegistry[aURISpec].push(aHUDId);
}
}
else {
this.uriRegistry[aURISpec] = [aHUDId];
}
},
/**
* When a display is being destroyed, unregister it first
*
* @param string aId
* @returns void
*/
unregisterDisplay: function HS_unregisterDisplay(aId)
{
// remove HUD DOM node and
// remove display references from local registries get the outputNode
var outputNode = this.mixins.getOutputNodeById(aId);
var parent = outputNode.parentNode;
var splitters = parent.querySelectorAll("splitter");
var len = splitters.length;
for (var i = 0; i < len; i++) {
if (splitters[i].getAttribute("class") == "hud-splitter") {
splitters[i].parentNode.removeChild(splitters[i]);
break;
}
}
parent.removeChild(outputNode);
delete this._headsUpDisplays[aId];
let displays = this.displays();
var uri = this.displayRegistry[aId];
var specHudArr = this.uriRegistry[uri];
len = specHudArr.length;
for (var i = 0; i < len; i++) {
if (specHudArr[i] == aId) {
specHudArr.splice(i, 1);
}
}
delete displays[aId];
delete this.displayRegistry[aId];
},
/**
* Shutdown all HeadsUpDisplays on xpcom-shutdown
*
* @returns void
*/
shutdown: function HS_shutdown()
{
for (var displayId in this._headsUpDisplays) {
this.unregisterDisplay(displayId);
}
},
/**
* get the nsIDOMNode outputNode via a nsIURI.spec
*
* @param string aURISpec
* @returns nsIDOMNode
*/
getDisplayByURISpec: function HS_getDisplayByURISpec(aURISpec)
{
// TODO: what about data:uris? see bug 568626
var hudIds = this.uriRegistry[aURISpec];
if (hudIds.length == 1) {
// only one HUD connected to this URISpec
return this.getHeadsUpDisplay(hudIds[0]);
}
else {
// TODO: how to determine more fully the origination of this activity?
// see bug 567165
return this.getHeadsUpDisplay(hudIds[0]);
}
},
/**
* Gets HUD DOM Node
* @param string id
* The Heads Up Display DOM Id
* @returns nsIDOMNode
*/
getHeadsUpDisplay: function HS_getHeadsUpDisplay(aId)
{
return this.mixins.getOutputNodeById(aId);
},
/**
* gets the nsIDOMNode outputNode by ID via the gecko app mixins
*
* @param string aId
* @returns nsIDOMNode
*/
getOutputNodeById: function HS_getOutputNodeById(aId)
{
return this.mixins.getOutputNodeById(aId);
},
/**
* Gets an object that contains active DOM Node Ids for all Heads Up Displays
*
* @returns object
*/
displays: function HS_displays() {
return this._headsUpDisplays;
},
/**
* Get an array of HUDIds that match a uri.spec
*
* @param string aURISpec
* @returns array
*/
getHUDIdsForURISpec: function HS_getHUDIdsForURISpec(aURISpec)
{
try {
return this.uriRegistry[aURISpec];
}
catch (ex){
return [];
}
},
/**
* Gets an array that contains active DOM Node Ids for all HUDs
* @returns array
*/
displaysIndex: function HS_displaysIndex()
{
var props = [];
for (var prop in this._headsUpDisplays) {
props.push(prop);
}
return props;
},
/**
* get the current filter string for the HeadsUpDisplay
*
* @param string aHUDId
* @returns string
*/
getFilterStringByHUDId: function HS_getFilterStringbyHUDId(aHUDId) {
var hud = this.getHeadsUpDisplay(aHUDId);
var filterStr = hud.querySelectorAll(".hud-filter-box")[0].value;
if (!filterStr) {
return null;
}
return filterStr;
},
/**
* The filter strings per HeadsUpDisplay
*
*/
hudFilterStrings: {},
/**
* Update the filter text in the internal tracking object for all
* filter strings
*
* @param nsIDOMNode aTextBoxNode
* @returns void
*/
updateFilterText: function HS_updateFiltertext(aTextBoxNode)
{
var hudId = aTextBoxNode.getAttribute(hudId);
this.hudFilterStrings[hudId] = aTextBoxNode.value || null;
},
/**
* Filter each message being logged into the console
*
* @param string aFilterString
* @param nsIDOMNode aMessageNode
* @returns JS Object
*/
filterLogMessage:
function HS_filterLogMessage(aFilterString, aMessageNode)
{
aFilterString = aFilterString.toLowerCase();
var messageText = aMessageNode.innerHTML.toLowerCase();
var idx = messageText.indexOf(aFilterString);
if (idx > -1) {
return { strLength: aFilterString.length, strIndex: idx };
}
else {
return null;
}
},
/**
* Get the filter toolbar from a HeadsUpDisplay
*
* @param string aHUDId
* @returns nsIDOMNode
*/
getFilterTextBox: function HS_getFilterTextBox(aHUDId)
{
var hud = this.getHeadsUpDisplay(aHUDId);
return hud.querySelectorAll(".hud-filter-box")[0];
},
/**
* Logs a HUD-generated console message
* @param object aMessage
* The message to log, which is a JS object, this is the
* "raw" log message
* @param nsIDOMNode aConsoleNode
* The output DOM node to log the messageNode to
* @param nsIDOMNode aMessageNode
* The message DOM Node that will be appended to aConsoleNode
* @returns void
*/
logHUDMessage:
function
HS_logHUDMessage(aMessage,
aConsoleNode,
aMessageNode,
aFilterState,
aFilterString)
{
if (!aFilterState) {
// do not log anything
return;
}
if (!aMessage) {
throw new Error(ERRORS.MISSING_ARGS);
}
if (aFilterString) {
var filtered = this.filterLogMessage(aFilterString, aMessageNode);
if (filtered) {
// we have successfully filtered a message, we need to log it
aConsoleNode.appendChild(aMessageNode);
aMessageNode.scrollIntoView(false);
}
else {
// we need to ignore this message by changing its css class - we are
// still logging this, it is just hidden
var hiddenMessage = hideLogMessage(aMessageNode);
aConsoleNode.appendChild(hiddenMessage);
}
}
else {
// log everything
aConsoleNode.appendChild(aMessageNode);
aMessageNode.scrollIntoView(false);
}
// store this message in the storage module:
var entry = this.storage.recordEntry(aMessage.hudId, aMessage);
},
/**
* logs a message to the Heads Up Display that originates
* in the nsIConsoleService
*
* @param nsIConsoleMessage aMessage
* @param nsIDOMNode aConsoleNode
* @param nsIDOMNode aMessageNode
* @returns void
*/
logConsoleMessage:
function
HS_logConsoleMessage(aMessage,
aConsoleNode,
aMessageNode,
aFilterState,
aFilterString)
{
if (aFilterState){
aConsoleNode.appendChild(aMessageNode);
aMessageNode.scrollIntoView(false);
}
// store this message in the storage module:
this.storage.recordEntry(aMessage.hudId, aMessage);
},
/**
* Logs a Message.
* @param aMessage
* The message to log, which is a JS object, this is the
* "raw" log message
* @param aConsoleNode
* The output DOM node to log the messageNode to
* @param The message DOM Node that will be appended to aConsoleNode
* @returns void
*/
logMessage: function HS_logMessage(aMessage, aConsoleNode, aMessageNode)
{
if (!aMessage) {
throw new Error(ERRORS.MISSING_ARGS);
}
var hud = this.getHeadsUpDisplay(aMessage.hudId);
// check filter before logging to the outputNode
var filterState = this.getFilterState(aMessage.hudId, aMessage.logLevel);
var filterString = this.getFilterStringByHUDId(aMessage.hudId);
switch (aMessage.origin) {
case "network":
case "HUDConsole":
this.logHUDMessage(aMessage, aConsoleNode, aMessageNode, filterState, filterString);
break;
case "console-listener":
this.logHUDMessage(aMessage, aConsoleNode, aMessageNode, filterState, filterString);
break;
default:
// noop
break;
}
},
/**
* report consoleMessages recieved via the HUDConsoleObserver service
* @param nsIConsoleMessage aConsoleMessage
* @returns void
*/
reportConsoleServiceMessage:
function HS_reportConsoleServiceMessage(aConsoleMessage)
{
this.logActivity("console-listener", null, aConsoleMessage);
},
/**
* report scriptErrors recieved via the HUDConsoleObserver service
* @param nsIScriptError aScriptError
* @returns void
*/
reportConsoleServiceContentScriptError:
function HS_reportConsoleServiceContentScriptError(aScriptError)
{
try {
var uri = Services.io.newURI(aScriptError.sourceName, null, null);
}
catch(ex) {
var uri = { spec: "" };
}
this.logActivity("console-listener", uri, aScriptError);
},
/**
* generates an nsIScriptError
*
* @param object aMessage
* @param integer flag
* @returns nsIScriptError
*/
generateConsoleMessage:
function HS_generateConsoleMessage(aMessage, flag)
{
let message = scriptError; // nsIScriptError
message.init(aMessage.message, null, null, 0, 0, flag,
"HUDConsole");
return message;
},
/**
* Register a Gecko app's specialized ApplicationHooks object
*
* @returns void or throws "UNSUPPORTED APPLICATION" error
*/
registerApplicationHooks:
function HS_registerApplications(aAppName, aHooksObject)
{
switch(aAppName) {
case "FIREFOX":
this.applicationHooks['FIREFOX'] = aHooksObject;
return;
default:
throw new Error("MOZ APPLICATION UNSUPPORTED");
}
},
get application()
{
return appName();
},
/**
* Registry of ApplicationHooks used by specified Gecko Apps
*
* @returns Specific Gecko 'ApplicationHooks' Object/Mixin
*/
applicationHooks: {
// method registry that keeps Mozilla-application-specific
// methods for showing and hiding the UI, and other
// application functionality.
FIREFOX: {},
},
/**
* Given an nsIChannel, return the corresponding nsILoadContext
*
* @param nsIChannel aChannel
* @returns nsILoadContext
*/
getLoadContext: function HS_getLoadContext(aChannel)
{
if (!aChannel) {
return null;
}
var loadContext;
var callbacks = aChannel.notificationCallbacks;
loadContext =
aChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
if (!loadContext) {
loadContext =
aChannel.QueryInterface(Ci.nsIRequest).loadGroup.notificationCallbacks
.getInterface(Ci.nsILoadContext);
}
return loadContext;
},
/**
* Given an nsILoadContext, return the corresponding nsIDOMWindow
*
* @param nsILoadContext aLoadContext
* @returns nsIDOMWindow
*/
getWindowFromContext: function HS_getWindowFromContext(aLoadContext)
{
if (!aLoadContext) {
throw new Error("loadContext is null");
}
if (aLoadContext.isContent) {
if (aLoadContext.associatedWindow) {
return aLoadContext.associatedWindow;
}
else if (aLoadContext.topWindow) {
return aLoadContext.topWindow;
}
}
throw new Error("Cannot get window from " + aLoadContext);
},
getChromeWindowFromContentWindow:
function HS_getChromeWindowFromContentWindow(aContentWindow)
{
if (!aContentWindow) {
throw new Error("Cannot get contentWindow via nsILoadContext");
}
var win = aContentWindow.QueryInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
return win;
},
/**
* get the outputNode from the window object
*
* @param nsIDOMWindow aWindow
* @returns nsIDOMNode
*/
getOutputNodeFromWindow:
function HS_getOutputNodeFromWindow(aWindow)
{
var browser = gBrowser.getBrowserForDocument(aWindow.top.document);
var tabId = gBrowser.getNotificationBox(browser).getAttribute("id");
var hudId = "hud_" + tabId;
var displayNode = this.getHeadsUpDisplay(hudId);
return displayNode.querySelectorAll(".hud-output-node")[0];
},
/**
* Try to get the outputNode via the nsIRequest
* XXX: not sure if this is helping - needs tests
* @param nsIRequest aRequest
* @returns nsIDOMNode
*/
getOutputNodeFromRequest: function HS_getOutputNodeFromRequest(aRequest)
{
var context = this.getLoadContext(aRequest);
var window = this.getWindowFromContext(context);
return this.getOutputNodeFromWindow(window);
},
getLoadContextFromChannel: function HS_getLoadContextFromChannel(aChannel)
{
try {
return aChannel.QueryInterface(Ci.nsIChannel).notificationCallbacks.getInterface(Ci.nsILoadContext);
}
catch (ex) {
// noop, keep this output quiet. see bug 552140
}
try {
return aChannel.QueryInterface(Ci.nsIChannel).loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
}
catch (ex) {
// noop, keep this output quiet. see bug 552140
}
return null;
},
getWindowFromLoadContext:
function HS_getWindowFromLoadContext(aLoadContext)
{
if (aLoadContext.topWindow) {
return aLoadContext.topWindow;
}
else {
return aLoadContext.associatedWindow;
}
},
/**
* Begin observing HTTP traffic that we care about,
* namely traffic that originates inside any context that a Heads Up Display
* is active for.
*/
startHTTPObservation: function HS_httpObserverFactory()
{
// creates an observer for http traffic
var self = this;
var httpObserver = {
observeActivity :
function (aChannel, aActivityType, aActivitySubtype,
aTimestamp, aExtraSizeData, aExtraStringData)
{
var loadGroup;
if (aActivityType ==
activityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION) {
try {
var loadContext = self.getLoadContextFromChannel(aChannel);
// TODO: much of this is under heavy development
// see bug 552140
var window = self.getWindowFromLoadContext(loadContext);
window = XPCNativeWrapper.unwrap(window);
var chromeWin = self.getChromeWindowFromContentWindow(window);
var vboxes =
chromeWin.document.getElementsByTagName("vbox");
var hudId;
for (var i = 0; i < vboxes.length; i++) {
if (vboxes[i].getAttribute("class") == "hud-box") {
hudId = vboxes[i].getAttribute("id");
}
}
loadGroup = self.getLoadGroup(hudId);
}
catch (ex) {
loadGroup = aChannel.QueryInterface(Ci.nsIChannel)
.QueryInterface(Ci.nsIRequest).loadGroup;
}
if (!loadGroup) {
return;
}
aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
var transCodes = this.httpTransactionCodes;
var httpActivity = {
channel: aChannel,
loadGroup: loadGroup,
type: aActivityType,
subType: aActivitySubtype,
timestamp: aTimestamp,
extraSizeData: aExtraSizeData,
extraStringData: aExtraStringData,
stage: transCodes[aActivitySubtype],
};
if (aActivitySubtype ==
activityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER ) {
// create a unique ID to track this transaction and be able to
// update the logged node with subsequent http transactions
httpActivity.httpId = self.sequenceId();
let loggedNode =
self.logActivity("network", aChannel.URI, httpActivity);
self.httpTransactions[aChannel] =
new Number(httpActivity.httpId);
}
}
},
httpTransactionCodes: {
0x5001: "REQUEST_HEADER",
0x5002: "REQUEST_BODY_SENT",
0x5003: "RESPONSE_START",
0x5004: "RESPONSE_HEADER",
0x5005: "RESPONSE_COMPLETE",
0x5006: "TRANSACTION_CLOSE",
}
};
activityDistributor.addObserver(httpObserver);
},
// keep tracked of trasactions where the request header was logged
// update logged transactions thereafter.
httpTransactions: {},
/**
* Logs network activity
*
* @param nsIURI aURI
* @param object aActivityObject
* @returns void
*/
logNetActivity: function HS_logNetActivity(aType, aURI, aActivityObject)
{
var displayNode, outputNode, hudId;
try {
displayNode =
this.getDisplayByLoadGroup(aActivityObject.loadGroup,
{URI: aURI}, aActivityObject);
if (!displayNode) {
return;
}
outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
hudId = displayNode.getAttribute("id");
if (!outputNode) {
outputNode = this.getOutputNodeFromRequest(aActivityObject.request);
hudId = outputNode.ownerDocument.querySelectorAll(".hud-box")[0]
.getAttribute("id");
}
// check if network activity logging is "on":
if (!this.getFilterState(hudId, "network")) {
return;
}
// get an id to attach to the dom node for lookup of node
// when updating the log entry with additional http transactions
var domId = "hud-log-node-" + this.sequenceId();
var message = { logLevel: aType,
activityObj: aActivityObject,
hudId: hudId,
origin: "network",
domId: domId,
};
var msgType = this.getStr("typeNetwork");
var msg = msgType + " " +
aActivityObject.channel.requestMethod +
" " +
aURI.spec;
message.message = msg;
var messageObject =
this.messageFactory(message, aType, outputNode, aActivityObject);
this.logMessage(messageObject.messageObject, outputNode, messageObject.messageNode);
}
catch (ex) {
Cu.reportError(ex);
}
},
/**
* Logs console listener activity
*
* @param nsIURI aURI
* @param object aActivityObject
* @returns void
*/
logConsoleActivity: function HS_logConsoleActivity(aURI, aActivityObject)
{
var displayNode, outputNode, hudId;
try {
var hudIds = this.uriRegistry[aURI.spec];
hudId = hudIds[0];
}
catch (ex) {
// TODO: uri spec is not tracked becasue the net request is
// using a different loadGroup
// see bug 568034
if (!displayNode) {
return;
}
}
var _msgLogLevel = this.scriptMsgLogLevel[aActivityObject.flags];
var msgLogLevel = this.getStr(_msgLogLevel);
// check if we should be logging this message:
var filterState = this.getFilterState(hudId, msgLogLevel);
if (!filterState) {
// Ignore log message
return;
}
// in this case, the "activity object" is the
// nsIScriptError or nsIConsoleMessage
var message = {
activity: aActivityObject,
origin: "console-listener",
hudId: hudId,
};
try {
var logLevel = this.scriptErrorFlags[aActivityObject.flags];
}
catch (ex) {
var logLevel = "warn";
}
var lineColSubs = [aActivityObject.columnNumber,
aActivityObject.lineNumber];
var lineCol = this.getFormatStr("errLineCol", lineColSubs);
var errFileSubs = [aActivityObject.sourceName];
var errFile = this.getFormatStr("errFile", errFileSubs);
var msgCategory = this.getStr("msgCategory");
message.logLevel = logLevel;
message.level = logLevel;
message.message = msgLogLevel + " " +
aActivityObject.errorMessage + " " +
errFile + " " +
lineCol + " " +
msgCategory + " " + aActivityObject.category;
displayNode = this.getHeadsUpDisplay(hudId);
outputNode = displayNode.querySelectorAll(".hud-output-node")[0];
var messageObject =
this.messageFactory(message, message.level, outputNode, aActivityObject);
this.logMessage(messageObject.messageObject, outputNode, messageObject.messageNode);
},
/**
* Parse log messages for origin or listener type
* Get the correct outputNode if it exists
* Finally, call logMessage to write this message to
* storage and optionally, a DOM output node
*
* @param string aType
* @param nsIURI aURI
* @param object (or nsIScriptError) aActivityObj
* @returns void
*/
logActivity: function HS_logActivity(aType, aURI, aActivityObject)
{
var displayNode, outputNode, hudId;
if (aType == "network") {
var result = this.logNetActivity(aType, aURI, aActivityObject);
}
else if (aType == "console-listener") {
this.logConsoleActivity(aURI, aActivityObject);
}
},
/**
* update loadgroup when the window object is re-created
*
* @param string aId
* @param nsILoadGroup aLoadGroup
* @returns void
*/
updateLoadGroup: function HS_updateLoadGroup(aId, aLoadGroup)
{
if (this.loadGroups[aId] == undefined) {
this.loadGroups[aId] = { id: aId,
loadGroup: Cu.getWeakReference(aLoadGroup) };
}
else {
this.loadGroups[aId].loadGroup = Cu.getWeakReference(aLoadGroup);
}
},
/**
* gets the load group that corresponds to a HUDId
*
* @param string aId
* @returns nsILoadGroup
*/
getLoadGroup: function HS_getLoadGroup(aId)
{
try {
return this.loadGroups[aId].loadGroup.get();
}
catch (ex) {
return null;
}
},
/**
* gets outputNode for a specific heads up display by loadGroup
*
* @param nsILoadGroup aLoadGroup
* @returns nsIDOMNode
*/
getDisplayByLoadGroup:
function HS_getDisplayByLoadGroup(aLoadGroup, aChannel, aActivityObject)
{
if (!aLoadGroup) {
return null;
}
var trackedLoadGroups = this.getAllLoadGroups();
var len = trackedLoadGroups.length;
for (var i = 0; i < len; i++) {
try {
var unwrappedLoadGroup =
XPCNativeWrapper.unwrap(trackedLoadGroups[i].loadGroup);
if (aLoadGroup == unwrappedLoadGroup) {
return this.getOutputNodeById(trackedLoadGroups[i].hudId);
}
}
catch (ex) {
// noop
}
}
// TODO: also need to check parent loadGroup(s) incase of iframe activity?;
// see bug 568643
return null;
},
/**
* gets all nsILoadGroups that are being tracked by this service
* the loadgroups are matched to HUDIds in an object and an array is returned
* @returns array
*/
getAllLoadGroups: function HS_getAllLoadGroups()
{
var loadGroups = [];
for (var hudId in this.loadGroups) {
let loadGroupObj = { loadGroup: this.loadGroups[hudId].loadGroup.get(),
hudId: this.loadGroups[hudId].id,
};
loadGroups.push(loadGroupObj);
}
return loadGroups;
},
/**
* gets the DOM Node that maps back to what context/tab that
* activity originated via the URI
*
* @param nsIURI aURI
* @returns nsIDOMNode
*/
getActivityOutputNode: function HS_getActivityOutputNode(aURI)
{
// determine which outputNode activity tied to aURI should be logged to.
var display = this.getDisplayByURISpec(aURI.spec);
if (display) {
return this.getOutputNodeById(display);
}
else {
throw new Error("Cannot get outputNode by hudId");
}
},
/**
* possibly unneeded wrapper method that generates a LogMessage object
*
* @param object aMessage
* @param string aLevel
* @param nsIDOMNode aOutputNode
* @param object aActivityObject
* @returns
*/
messageFactory:
function messageFactory(aMessage, aLevel, aOutputNode, aActivityObject)
{
// generate a LogMessage object
return new LogMessage(aMessage, aLevel, aOutputNode, aActivityObject);
},
/**
* Initialize the JSTerm object to create a JS Workspace
*
* @param nsIDOMWindow aContext
* @param nsIDOMNode aParentNode
* @returns void
*/
initializeJSTerm: function HS_initializeJSTerm(aContext, aParentNode)
{
// create Initial JS Workspace:
var context = Cu.getWeakReference(aContext);
var firefoxMixin = new JSTermFirefoxMixin(context, aParentNode);
var jsTerm = new JSTerm(context, aParentNode, firefoxMixin);
// TODO: injection of additional functionality needs re-thinking/api
// see bug 559748
},
/**
* Passed a HUDId, the correspoinding window is returned
*
* @param string aHUDId
* @returns nsIDOMWindow
*/
getContentWindowFromHUDId: function HS_getContentWindowFromHUDId(aHUDId)
{
var hud = this.getHeadsUpDisplay(aHUDId);
var nodes = hud.parentNode.childNodes;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].contentWindow) {
return nodes[i].contentWindow;
}
}
throw new Error("HS_getContentWindowFromHUD: Cannot get contentWindow");
},
/**
* attaches the DOMMutation listeners to a nsIDOMWindow object
*
* @param nsIDOMWindow aWindow
* @param string aHUDId
* @returns void
*/
attachMutationListeners:
function HS_attachMutationListeners(aWindow, aHUDId)
{
try {
// remove first in case it is on already
new ConsoleDOMListeners(aWindow, aHUDId, true);
// add mutation listeners
var domListeners = new ConsoleDOMListeners(aWindow, aHUDId);
}
catch (ex) {
Cu.reportError(ex);
}
},
/**
* removes DOMMutation listeners
*
* @param nsIDOMWindow aWindow
* @param string aHUDId
* @returns void
*/
removeMutationListeners:
function HS_removeMutationListeners(aWindow, aHUDId)
{
// turns off the listeners if active
try {
new ConsoleDOMListeners(aWindow, aHUDId, true);
}
catch (ex) {
Cu.reportError(ex);
}
},
/**
* toggle on and off teh DOMMutation listeners
*
* @param string aHUDId
* @returns void
*/
toggleMutationListeners: function HS_toggleMutationListeners(aHUDId)
{
// get the contentWindow from the HUDId
var window = this.getContentWindowFromHUDId(aHUDId);
var filterState = this.getFilterState(aHUDId, "mutation");
if (!filterState) {
// turn it off
this.removeMutationListeners(window);
}
else {
this.attachMutationListeners(window, aHUDId);
}
},
mutationListenerIndex: {
},
/**
* 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);
},
scriptErrorFlags: {
0: "error",
1: "warn",
2: "exception",
4: "strict"
},
/**
* replacement strings (L10N)
*/
scriptMsgLogLevel: {
0: "typeError",
1: "typeWarning",
2: "typeException",
4: "typeStrict",
}
};
//////////////////////////////////////////////////////////////////////////
// HeadsUpDisplay
//////////////////////////////////////////////////////////////////////////
/*
* HeadsUpDisplay 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
* */
function HeadsUpDisplay(aConfig)
{
// sample config: { parentNode: aDOMNode,
// // or
// parentNodeId: "myHUDParent123",
//
// placement: "appendChild"
// // or
// placement: "insertBefore",
// placementChildNodeIndex: 0,
// }
//
// or, just create a new console - as there is already a HUD in place
// config: { hudNode: existingHUDDOMNode,
// consoleOnly: true,
// contentWindow: aWindow
// }
this.stringBundle = Services.strings.createBundle(HUD_STRINGS_URI);
if (aConfig.consoleOnly) {
this.HUDBox = aConfig.hudNode;
this.parentNode = aConfig.hudNode.parentNode;
this.notificationBox = this.parentNode;
this.contentWindow = aConfig.contentWindow;
this.uriSpec = aConfig.contentWindow.location.href;
this.reattachConsole();
return;
}
this.HUDBox = null;
if (aConfig.parentNode) {
// TODO: need to replace these DOM calls with internal functions
// that operate on each application's node structure
// better yet, we keep these functions in a "bridgeModule" or the HUDService
// to keep a registry of nodeGetters for each application
// see bug 568647
this.parentNode = aConfig.parentNode;
this.notificationBox = aConfig.parentNode;
this.chromeDocument = aConfig.parentNode.ownerDocument;
this.contentWindow = aConfig.contentWindow;
this.uriSpec = aConfig.contentWindow.location.href;
this.hudId = "hud_" + aConfig.parentNode.getAttribute("id");
}
else {
// parentNodeId is the node's id where we attach the HUD
// TODO: is the "navigator:browser" below used in all Gecko Apps?
// see bug 568647
let windowEnum = Services.wm.getEnumerator("navigator:browser");
let parentNode;
let contentDocument;
let contentWindow;
let chromeDocument;
// TODO: the following part is still very Firefox specific
// see bug 568647
while (windowEnum.hasMoreElements()) {
let window = windowEnum.getNext();
try {
let gBrowser = window.gBrowser;
let _browsers = gBrowser.browsers;
let browserLen = _browsers.length;
for (var i = 0; i < browserLen; i++) {
var _notificationBox = gBrowser.getNotificationBox(_browsers[i]);
this.notificationBox = _notificationBox;
if (_notificationBox.getAttribute("id") == aConfig.parentNodeId) {
this.parentNodeId = _notificationBox.getAttribute("id");
this.hudId = "hud_" + this.parentNodeId;
parentNode = _notificationBox;
this.contentDocument =
_notificationBox.childNodes[0].contentDocument;
this.contentWindow =
_notificationBox.childNodes[0].contentWindow;
this.uriSpec = aConfig.contentWindow.location.href;
this.chromeDocument =
_notificationBox.ownerDocument;
break;
}
}
}
catch (ex) {
Cu.reportError(ex);
}
if (parentNode) {
break;
}
}
if (!parentNode) {
throw new Error(this.ERRORS.PARENTNODE_NOT_FOUND);
}
this.parentNode = parentNode;
}
// create XUL, HTML and textNode Factories:
try {
this.HTMLFactory = NodeFactory("html", "html", this.chromeDocument);
}
catch(ex) {
Cu.reportError(ex);
}
this.XULFactory = NodeFactory("xul", "xul", this.chromeDocument);
this.textFactory = NodeFactory("text", "xul", this.chromeDocument);
// create a panel dynamically and attach to the parentNode
let hudBox = this.createHUD();
let splitter = this.chromeDocument.createElement("splitter");
splitter.setAttribute("collapse", "before");
splitter.setAttribute("resizeafter", "flex");
splitter.setAttribute("class", "hud-splitter");
let grippy = this.chromeDocument.createElement("grippy");
this.notificationBox.insertBefore(splitter,
this.notificationBox.childNodes[1]);
splitter.appendChild(grippy);
let console = this.createConsole();
this.contentWindow.wrappedJSObject.console = console;
// check prefs to see if we should attact mutation listeners
var mutationFlag = HUDService.getFilterState(this.hudId, "mutation");
if (mutationFlag) {
HUDService.attachMutationListeners(this.contentWindow, this.hudId);
}
// create the JSTerm input element
try {
this.createConsoleInput(this.contentWindow, this.consoleWrap, this.outputNode);
}
catch (ex) {
Cu.reportError(ex);
}
}
HeadsUpDisplay.prototype = {
/**
* L10N shortcut function
*
* @param string aName
* @returns string
*/
getStr: function HUD_getStr(aName)
{
return this.stringBundle.GetStringFromName(aName);
},
/**
* L10N shortcut function
*
* @param string aName
* @param array aArray
* @returns string
*/
getFormatStr: function HUD_getFormatStr(aName, aArray)
{
return this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
},
/**
* creates and attaches the console input node
*
* @param nsIDOMWindow aContext
* XXX: (perhaps aContext is bad nomenclature?)
* @returns void
*/
createConsoleInput:
function HUD_createConsoleInput(aContext, aParentNode, aExistingConsole)
{
var context = Cu.getWeakReference(aContext);
if (appName() == "FIREFOX") {
let outputCSSClassOverride = "hud-msg-node hud-console";
let mixin = new JSTermFirefoxMixin(context, aParentNode, aExistingConsole, outputCSSClassOverride);
let inputNode = new JSTerm(context, aParentNode, mixin);
}
else {
throw new Error("Unsupported Gecko Application");
}
},
/**
* Re-attaches a console when the contentWindow is recreated
*
* @returns void
*/
reattachConsole: function HUD_reattachConsole()
{
this.hudId = this.HUDBox.getAttribute("id");
// set outputNode
this.outputNode = this.HUDBox.querySelectorAll(".hud-output-node")[0];
this.chromeDocument = this.HUDBox.ownerDocument;
if (this.outputNode) {
// createConsole
this.createConsole();
}
else {
throw new Error("Cannot get output node");
}
},
/**
* Gets the loadGroup for the contentWindow
*
* @returns nsILoadGroup
*/
get loadGroup()
{
var loadGroup = this.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocumentLoader).loadGroup;
return loadGroup;
},
/**
* Shortcut to make HTML nodes
*
* @param string aTag
* @returns nsIDOMNode
*/
makeHTMLNode:
function HUD_makeHTMLNode(aTag)
{
try {
return this.HTMLFactory(aTag);
}
catch (ex) {
var ns = ELEMENT_NS;
var nsUri = ELEMENT_NS_URI;
var tag = ns + aTag;
return this.chromeDocument.createElementNS(nsUri, tag);
}
},
/**
* Shortcut to make XUL nodes
*
* @param string aTag
* @returns nsIDOMNode
*/
makeXULNode:
function HUD_makeXULNode(aTag)
{
return this.XULFactory(aTag);
},
/**
* Clears the HeadsUpDisplay output node of any log messages
*
* @returns void
*/
clearConsoleOutput: function HUD_clearConsoleOutput()
{
for each (var node in this.outputNode.childNodes) {
this.outputNode.removeChild(node);
}
},
/**
* Build the UI of each HeadsUpDisplay
*
* @returns nsIDOMNode
*/
makeHUDNodes: function HUD_makeHUDNodes()
{
let self = this;
this.HUDBox = this.makeXULNode("vbox");
this.HUDBox.setAttribute("id", this.hudId);
this.HUDBox.setAttribute("class", "hud-box");
let outerWrap = this.makeXULNode("vbox");
outerWrap.setAttribute("class", "hud-outer-wrapper");
outerWrap.setAttribute("flex", "1");
let consoleCommandSet = this.makeXULNode("commandset");
outerWrap.appendChild(consoleCommandSet);
let consoleWrap = this.makeXULNode("vbox");
this.consoleWrap = consoleWrap;
consoleWrap.setAttribute("class", "hud-console-wrapper");
consoleWrap.setAttribute("flex", "1");
this.outputNode = this.makeXULNode("vbox");
this.outputNode.setAttribute("class", "hud-output-node");
this.outputNode.setAttribute("flex", "1");
this.filterBox = this.makeXULNode("textbox");
this.filterBox.setAttribute("class", "hud-filter-box");
this.filterBox.setAttribute("hudId", this.hudId);
this.filterClearButton = this.makeXULNode("button");
this.filterClearButton.setAttribute("class", "hud-filter-clear");
this.filterClearButton.setAttribute("label", this.getStr("stringFilterClear"));
this.filterClearButton.setAttribute("hudId", this.hudId);
this.setFilterTextBoxEvents();
this.consoleClearButton = this.makeXULNode("button");
this.consoleClearButton.setAttribute("class", "hud-console-clear");
this.consoleClearButton.setAttribute("label", this.getStr("btnClear"));
this.consoleClearButton.setAttribute("buttonType", "clear");
this.consoleClearButton.setAttribute("hudId", this.hudId);
var command = "HUDConsoleUI.command(this)";
this.consoleClearButton.setAttribute("oncommand", command);
this.filterPrefs = HUDService.getDefaultFilterPrefs(this.hudId);
let consoleFilterToolbar = this.makeFilterToolbar();
consoleFilterToolbar.setAttribute("mode", "text");
consoleFilterToolbar.setAttribute("id", "viewGroup");
this.consoleFilterToolbar = consoleFilterToolbar;
consoleWrap.appendChild(consoleFilterToolbar);
consoleWrap.appendChild(this.outputNode);
outerWrap.appendChild(consoleWrap);
this.jsTermParentNode = outerWrap;
this.HUDBox.appendChild(outerWrap);
return this.HUDBox;
},
/**
* sets the click events for all binary toggle filter buttons
*
* @returns void
*/
setFilterTextBoxEvents: function HUD_setFilterTextBoxEvents()
{
var self = this;
function keyPress(aEvent)
{
HUDService.updateFilterText(aEvent.target);
}
this.filterBox.addEventListener("keydown", keyPress, false);
function filterClick(aEvent) {
self.filterBox.value = "";
}
this.filterClearButton.addEventListener("click", filterClick, false);
},
/**
* Make the filter toolbar where we can toggle logging filters
*
* @returns nsIDOMNode
*/
makeFilterToolbar: function HUD_makeFilterToolbar()
{
let self = this;
let buttons = ["Mutation", "Network", "CSSParser",
"Exception", "Error",
"Info", "Warn", "Log",];
function makeButton(aName, aType)
{
let prefKey = aName.toLowerCase();
let btn = self.makeXULNode("toolbarbutton");
if (aType == "checkbox") {
btn.setAttribute("type", aType);
}
btn.setAttribute("hudId", self.hudId);
btn.setAttribute("buttonType", prefKey);
btn.setAttribute("class", "toolbarbutton-text toolbarbutton-1 bookmark-item hud-filter-btn");
let key = "btn" + aName;
btn.setAttribute("label", self.getStr(key));
key = "tip" + aName;
btn.setAttribute("tooltip", self.getStr(key));
if (aType == "checkbox") {
btn.setAttribute("checked", self.filterPrefs[prefKey]);
function toggle(btn) {
self.consoleFilterCommands.toggle(btn);
};
btn.setAttribute("oncommand", "HUDConsoleUI.toggleFilter(this);");
}
else {
var command = "HUDConsoleUI.command(this)";
btn.setAttribute("oncommand", command);
}
return btn;
}
let toolbar = this.makeXULNode("toolbar");
toolbar.setAttribute("class", "hud-console-filter-toolbar");
let label = this.makeXULNode("description");
label.setAttribute("class", "hud-main-label");
label.setAttribute("value", this.getStr("hudTitle"));
toolbar.appendChild(label);
toolbar.appendChild(this.consoleClearButton);
let btn;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i] == "Clear") {
btn = makeButton(buttons[i], "plain");
}
else {
btn = makeButton(buttons[i], "checkbox");
}
toolbar.appendChild(btn);
}
toolbar.appendChild(this.filterBox);
toolbar.appendChild(this.filterClearButton);
return toolbar;
},
createHUD: function HUD_createHUD()
{
let self = this;
if (this.HUDBox) {
return this.HUDBox;
}
else {
this.makeHUDNodes();
let nodes = this.notificationBox.insertBefore(this.HUDBox,
this.notificationBox.childNodes[0]);
return this.HUDBox;
}
},
get console()
{
if (this._console) {
return this._console;
}
else {
return this.createConsole();
}
},
getLogCount: function HUD_getLogCount()
{
return this.outputNode.childNodes.length;
},
getLogNodes: function HUD_getLogNodes()
{
return this.outputNode.childNodes;
},
/**
* This console will accept a message, get the tab's meta-data and send
* properly-formatted message object to the service identifying
* where it came from, etc...
*
* @returns console
*/
createConsole: function HUD_createConsole()
{
let self = this;
let Console = function HUDConsole ()
{
this.hudId = self.hudId;
this.outputNode = self.outputNode;
this.chromeDocument = self.chromeDocument;
this.makeHTMLNode = self.makeHTMLNode;
this.created = new Date();
self._console = this;
HUDService.updateLoadGroup(this.hudId, self.loadGroup);
this.consoleUtils = new ConsoleUtils();
};
Console.prototype = {
created: null,
log: function console_log(aMessage)
{
this.message = aMessage;
this.sendToHUDService("log");
},
info: function console_info(aMessage)
{
this.message = aMessage;
this.sendToHUDService("info");
},
warn: function console_warn(aMessage)
{
this.message = aMessage;
this.sendToHUDService("warn");
},
error: function console_error(aMessage)
{
this.message = aMessage;
this.sendToHUDService("error");
},
exception: function console_exception(aMessage)
{
this.message = aMessage;
this.sendToHUDService("exception");
},
timeStamp: function Console_timeStamp()
{
return this.consoleUtils.timeStamp(new Date());
},
sendToHUDService: function console_send(aLevel)
{
// check to see if logging is on for this level before logging!
var filterState = HUDService.getFilterState(this.hudId, aLevel);
if (!filterState) {
// Ignoring log message
return;
}
let self = this;
let ts = self.timeStamp();
let messageNode =
self.makeHTMLNode("div");
let klass = "hud-msg-node hud-" + aLevel;
messageNode.setAttribute("class", klass);
let timestampedMessage =
this.chromeDocument.createTextNode(ts + ": " + this.message);
messageNode.appendChild(timestampedMessage);
// need a constructor here to properly set all attrs
let messageObject = {
logLevel: aLevel,
hudId: self.hudId,
message: self.message,
timeStamp: ts,
origin: "HUDConsole",
};
HUDService.logMessage(messageObject, self.outputNode, messageNode);
}
};
return new Console();
},
ERRORS: {
HUD_BOX_DOES_NOT_EXIST: "Heads Up Display does not exist",
TAB_ID_REQUIRED: "Tab DOM ID is required",
PARENTNODE_NOT_FOUND: "parentNode element not found"
}
};
/**
* Creates a DOM Node factory for either XUL nodes or HTML nodes - as
* well as textNodes
* @param aFactoryType
* "xul" or "html"
* @returns DOM Node Factory function
*/
function NodeFactory(aFactoryType, aNameSpace, aDocument)
{
// aDocument is presumed to be a XULDocument
const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
if (aFactoryType == "text") {
function factory(aText) {
return aDocument.createTextNode(aText);
}
return factory;
}
else {
if (aNameSpace == "xul") {
function factory(aTag)
{
return aDocument.createElement(aTag);
}
return factory;
}
else {
function factory(aTag)
{
var tag = "html:" + aTag;
return aDocument.createElementNS(ELEMENT_NS_URI, tag);
}
return factory;
}
}
}
//////////////////////////////////////////////////////////////////////////
// JSTerm
//////////////////////////////////////////////////////////////////////////
/**
* JSTerm
*
* JavaScript Terminal: creates input nodes for console code interpretation
* and 'JS Workspaces'
*/
function JSTerm(aContext, aParentNode, aMixin)
{
// set the context, attach the UI by appending to aParentNode
this.application = appName();
this.context = aContext;
this.parentNode = aParentNode;
this.mixins = aMixin;
this.elementFactory =
NodeFactory("html", "html", aParentNode.ownerDocument);
this.xulElementFactory =
NodeFactory("xul", "xul", aParentNode.ownerDocument);
this.textFactory = NodeFactory("text", "xul", aParentNode.ownerDocument);
this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
this.historyIndex = 0;
this.historyPlaceHolder = 0; // this.history.length;
this.log = LogFactory("*** JSTerm:");
this.init();
}
JSTerm.prototype = {
init: function JST_init()
{
this.createSandbox();
this.inputNode = this.mixins.inputNode;
this.scrollToNode = this.mixins.scrollToNode;
let eventHandler = this.keyDown();
this.inputNode.addEventListener('keypress', eventHandler, false);
this.outputNode = this.mixins.outputNode;
if (this.mixins.cssClassOverride) {
this.cssClassOverride = this.mixins.cssClassOverride;
}
},
get codeInputString()
{
var str = this.inputNode.value;
return str;
},
generateUI: function JST_generateUI()
{
this.mixins.generateUI();
},
attachUI: function JST_attachUI()
{
this.mixins.attachUI();
},
mozApps: {
},
injectTerminalMethods: function JST_injectConsoleMethods()
{
var self = this;
var echo = function _echo(aStr) {
self.writeOutput(aStr);
};
var funcs = [{name:'echo', func: echo}];
for each(var method in consoleMethods) {
this._window.wrappedJSObject["jsterm"][method.name] = method.func;
}
},
createSandbox: function JST_setupSandbox()
{
// create a JS Sandbox out of this.context
this._window.wrappedJSObject.jsterm = {};
this.console = this._window.wrappedJSObject.console;
this.sandbox = new Cu.Sandbox(this._window);
this.sandbox.__proto__ = this._window.wrappedJSObject;
},
get _window()
{
return this.context.get().QueryInterface(Ci.nsIDOMWindowInternal);
},
execute: function JST_execute()
{
// attempt to execute the content of the inputNode
var str = this.inputNode.value;
if (!str) {
this.console.log("no value to execute");
return;
}
try {
var result =
Cu.evalInSandbox(str, this.sandbox, "default", "HUD Console", 1);
this.writeOutput(str);
this.history.push(str);
this.historyIndex++;
this.historyPlaceHolder = this.history.length;
if (result !== undefined) {
this.writeOutput(result);
}
}
catch (ex) {
this.writeOutput(ex);
this.console.error(ex);
}
this.inputNode.value = "";
},
writeOutput: function JST_writeOutput(aOutputMessage)
{
var node = this.elementFactory("div");
if (this.cssClassOverride) {
node.setAttribute("class", this.cssClassOverride);
}
else {
node.setAttribute("class", "jsterm-output-line");
}
var textNode = this.textFactory(aOutputMessage);
node.appendChild(textNode);
this.outputNode.appendChild(node);
node.scrollIntoView(false);
},
keyDown: function JSTF_keyDown(aEvent)
{
var self = this;
function handleKeyDown(aEvent) {
// ctrl-a
var setTimeout = aEvent.target.ownerDocument.defaultView.setTimeout;
let target = aEvent.target;
if (aEvent.ctrlKey && aEvent.charCode == 97) {
var tmp = self.codeInputString;
setTimeout(function() {
self.inputNode.value = tmp;
self.inputNode.setSelectionRange(0, 0);
},0);
}
else if (aEvent.ctrlKey && aEvent.charCode == 101) {
var tmp = self.codeInputString;
self.inputNode.value = "";
setTimeout(function(){
var endPos = tmp.length + 1;
self.inputNode.value = tmp;
},0);
}
else if (aEvent.shiftKey && aEvent.keyCode == 13) {
// shift-enter
// don't do anything; allow the shift-enter to insert
// a line break as normal
}
else if (aEvent.keyCode == 13) {
self.execute();
}
else if (aEvent.keyCode == 38) {
// up
// go up in history if at top or ctrl-up
if (self.caretInFirstLine()){
self.historyPeruse(true);
}
}
else if (aEvent.keyCode == 40) {
// down
// go down in history if at end or ctrl-down
if (self.caretInLastLine()){
self.historyPeruse(false);
}
}
else if (aEvent.keyCode == 9) {
// tab
// TODO: this.tabComplete();
// see bug 568649
var bool = aEvent.cancelable;
if (bool) {
aEvent.preventDefault();
}
else {
// noop
}
aEvent.target.focus();
}
}
return handleKeyDown;
},
historyPeruse: function JST_historyPeruse(aFlag) {
// Needs more work, see bug 568652
var len = this.history.length;
if (len == 0) {
return;
}
// Up Arrow key
if (aFlag == true) {
var idx = this.historyPlaceHolder--;
if (idx < -1) {
return;
}
var inputVal = this.history[idx -1];
if (inputVal){
this.inputNode.value = this.history[idx -1];
}
}
else {
var idx = this.historyPlaceHolder++;
if (idx > (len + 1)) {
return;
}
var inputVal = this.history[idx +1];
if (inputVal){
this.inputNode.value = this.history[idx +1];
}
}
},
refocus: function JSTF_refocus()
{
this.inputNode.blur();
this.inputNode.focus();
},
caretInFirstLine: function JSTF_caretInFirstLine()
{
var firstLineBreak = this.codeInputString.indexOf("\n");
return ((firstLineBreak == -1) ||
(this.codeInputString.selectionStart <= firstLineBreak));
},
caretInLastLine: function JSTF_caretInLastLine()
{
var lastLineBreak = this.codeInputString.lastIndexOf("\n");
return (this.inputNode.selectionEnd > lastLineBreak);
},
history: [],
tabComplete: function JSTF_tabComplete(aInputValue) {
// parse input value:
// TODO: see bug 568649
}
};
/**
* JSTermFirefoxMixin
*
* JavaScript Terminal Firefox Mixin
*
*/
function
JSTermFirefoxMixin(aContext, aParentNode, aExistingConsole, aCSSClassOverride)
{
// aExisting Console is the existing outputNode to use in favor of
// creating a new outputNode - this is so we can just attach the inputNode to
// a normal HeadsUpDisplay console output, and re-use code.
this.cssClassOverride = aCSSClassOverride;
this.context = aContext;
this.parentNode = aParentNode;
this.existingConsoleNode = aExistingConsole;
this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
if (aParentNode.ownerDocument) {
this.elementFactory =
NodeFactory("html", "html", aParentNode.ownerDocument);
this.xulElementFactory =
NodeFactory("xul", "xul", aParentNode.ownerDocument);
this.textFactory = NodeFactory("text", "xul", aParentNode.ownerDocument);
this.generateUI();
this.attachUI();
}
else {
throw new Error("aParentNode should be a DOM node with an ownerDocument property ");
}
}
JSTermFirefoxMixin.prototype = {
/**
* Generates and attaches the UI for an entire JS Workspace or
* just the input node used under the console output
*
* @returns void
*/
generateUI: function JSTF_generateUI()
{
let inputNode = this.xulElementFactory("textbox");
inputNode.setAttribute("class", "jsterm-input-node");
if (this.existingConsoleNode == undefined) {
// create elements
let term = this.elementFactory("div");
term.setAttribute("class", "jsterm-wrapper-node");
term.setAttribute("flex", "1");
let outputNode = this.elementFactory("div");
outputNode.setAttribute("class", "jsterm-output-node");
let scrollToNode = this.elementFactory("div");
scrollToNode.setAttribute("class", "jsterm-scroll-to-node");
// construction
outputNode.appendChild(scrollToNode);
term.appendChild(outputNode);
term.appendChild(inputNode);
this.scrollToNode = scrollToNode;
this.outputNode = outputNode;
this.inputNode = inputNode;
this.term = term;
}
else {
this.inputNode = inputNode;
this.term = inputNode;
this.outputNode = this.existingConsoleNode;
}
},
get inputValue()
{
return this.inputNode.value;
},
attachUI: function JSTF_attachUI()
{
this.parentNode.appendChild(this.term);
}
};
/////////////////////////////////////////////////////////////////////////
// HUDUtils
/////////////////////////////////////////////////////////////////////////
function LogFactory(aMessagePrefix)
{
function log(aMessage) {
var _msg = aMessagePrefix + " " + aMessage + "\n";
dump(_msg);
}
return log;
}
/**
* Creates a DOM Node factory for either XUL nodes or HTML nodes - as
* well as textNodes
* @param aFactoryType
* "xul" or "html"
* @returns DOM Node Factory function
*/
function NodeFactory(aFactoryType, aNameSpace, aDocument)
{
// aDocument is presumed to be a XULDocument
const ELEMENT_NS_URI = "http://www.w3.org/1999/xhtml";
if (aFactoryType == "text") {
function factory(aText) {
return aDocument.createTextNode(aText);
}
return factory;
}
else {
if (aNameSpace == "xul") {
function factory(aTag) {
return aDocument.createElement(aTag);
}
return factory;
}
else {
function factory(aTag) {
var tag = "html:" + aTag;
return aDocument.createElementNS(ELEMENT_NS_URI, tag);
}
return factory;
}
}
}
/**
* LogMessage represents a single message logged to the "outputNode" console
*/
function LogMessage(aMessage, aLevel, aOutputNode, aActivityObject)
{
if (!aOutputNode || !aOutputNode.ownerDocument) {
throw new Error("aOutputNode is required and should be type nsIDOMNode");
}
if (!aMessage.origin) {
throw new Error("Cannot create and log a message without an origin");
}
this.message = aMessage;
if (aMessage.domId) {
// domId is optional - we only need it if the logmessage is
// being asynchronously updated
this.domId = aMessage.domId;
}
this.activityObject = aActivityObject;
this.outputNode = aOutputNode;
this.level = aLevel;
this.origin = aMessage.origin;
this.elementFactory =
NodeFactory("html", "html", aOutputNode.ownerDocument);
this.xulElementFactory =
NodeFactory("xul", "xul", aOutputNode.ownerDocument);
this.textFactory = NodeFactory("text", "xul", aOutputNode.ownerDocument);
this.createLogNode();
}
LogMessage.prototype = {
/**
* create a console log div node
*
* @returns nsIDOMNode
*/
createLogNode: function LM_createLogNode()
{
this.messageNode = this.elementFactory("div");
var ts = this.timestamp();
var timestampedMessage = ts + ": " + this.message.message;
var messageTxtNode = this.textFactory(timestampedMessage);
this.messageNode.appendChild(messageTxtNode);
var klass = "hud-msg-node hud-" + this.level;
this.messageNode.setAttribute("class", klass);
var self = this;
var messageObject = {
logLevel: self.level,
message: self.message,
timestamp: ts,
activity: self.activityObject,
origin: self.origin,
hudId: self.message.hudId,
};
this.messageObject = messageObject;
},
timestamp: function LM_timestamp()
{
// TODO: L10N see bug 568656
// TODO: DUPLICATED CODE to be consolidated with the utils timestamping
// see bug 568657
function logDateString(d)
{
function pad(n, mil)
{
if (mil) {
return n < 100 ? '0' + n : n;
}
return n < 10 ? '0' + n : n;
}
return pad(d.getHours())+':'
+ pad(d.getMinutes())+':'
+ pad(d.getSeconds()) + ":"
+ pad(d.getMilliseconds(), true);
}
return logDateString(new Date());
}
};
/**
* Firefox-specific Application Hooks.
* Each Gecko-based application will need an object like this in
* order to use the Heads Up Display
*/
function FirefoxApplicationHooks()
{ }
FirefoxApplicationHooks.prototype = {
/**
* Firefox-specific method for getting an array of chrome Winow objects
*/
get chromeWindows()
{
var windows = [];
var enumerator = Services.ww.getWindowEnumerator(null);
while (enumerator.hasMoreElements()) {
windows.push(enumerator.getNext());
}
return windows;
},
/**
* Firefox-specific method for getting the DOM node (per tab) that message
* nodes are appended to.
* @param aId
* The DOM node's id.
*/
getOutputNodeById: function FAH_getOutputNodeById(aId)
{
if (!aId) {
throw new Error("FAH_getOutputNodeById: id is null!!");
}
var enumerator = Services.ww.getWindowEnumerator(null);
while (enumerator.hasMoreElements()) {
let window = enumerator.getNext();
let node = window.document.getElementById(aId);
if (node) {
return node;
}
}
throw new Error("Cannot get outputNode by id");
},
/**
* gets the current contentWindow (Firefox-specific)
*
* @returns nsIDOMWindow
*/
getCurrentContext: function FAH_getCurrentContext()
{
var window = Services.wm.getMostRecentWindow("navigator:browser");
return window;
}
};
/**
* ConsoleDOMListeners
* Attach DOM Mutation listeners to a document
* @param aWindow
*
* @returns void
*/
function ConsoleDOMListeners(aWindow, aHUDId, aRemoveBool)
{
this.hudId = aHUDId;
this.window = XPCNativeWrapper.unwrap(aWindow);
this.console = this.window.console;
this.document = this.window.document;
this.trackedEvents = ['DOMSubtreeModified',
'DOMNodeInserted',
'DOMNodeRemoved',
'DOMNodeRemovedFromDocument',
'DOMNodeInsertedIntoDocument',
'DOMAttrModified',
'DOMCharacterDataModified',
'DOMElementNameChanged',
'DOMAttributeNameChanged',
];
if (aRemoveBool) {
var removeFunc = this.removeAllListeners(aHUDId);
removeFunc();
}
this.init();
}
ConsoleDOMListeners.prototype = {
init: function CDL_init()
{
for (var event in this.trackedEvents) {
let evt = this.trackedEvents[event];
let callback = this.eventListenerFactory(evt);
this.document.addEventListener(evt, callback, false);
this.storeMutationFunc(this.hudId, callback, evt);
}
},
/**
* function factory that gnerates an event handler for DOM Mutations
*
* @param string aEventName
* @returns function
*/
eventListenerFactory: function CDL_eventListenerFactory(aEventName)
{
var self = this;
function callback(aEvent)
{
var nodeTag = aEvent.target.tagName;
var nodeClass = aEvent.target.getAttribute("class");
if (!nodeClass) {
nodeClass = "null";
}
var nodeId = aEvent.target.getAttribute("id");
if (!nodeId) {
nodeId = "null";
}
var message = "DOM Mutation Event: '"
+ aEventName + "'"
+ " on node. "
+ " id: " + nodeId
+ " class: " + nodeClass
+ " tag: " + nodeTag;
self.console.info(message);
}
return callback;
},
/**
* generates a function that removes all DOM Mutation listeners
* per HeadsUpDisplay
* XXX: needs some tweaks, see bug 568658
*
* @param string aHUDId
* @returns function
*/
removeAllListeners: function CDL_removeAllListeners(aHUDId)
{
var self = this;
function removeListeners()
{
for (var idx in HUDService.mutationEventFunctions[aHUDId]) {
let evtObj = HUDService.mutationEventFunctions[aHUDId][idx];
self.document.removeEventListener(evtObj.name, evtObj.func, false);
}
}
return removeListeners;
},
/**
* store a DOM Mutation function for later retrieval,
* removal and destruction
*
* @param string aHUDId
* @param function aFunc
* @param string aEventName
* @returns void
*/
storeMutationFunc:
function CDL_storeMutationFunc(aHUDId, aFunc, aEventName)
{
var evtObj = {func: aFunc, name: aEventName};
if (HUDService.mutationEventFunctions[aHUDId]) {
HUDService.mutationEventFunctions[aHUDId].push(evtObj);
}
else {
HUDService.mutationEventFunctions[aHUDId] = [];
HUDService.mutationEventFunctions[aHUDId].push(evtObj);
}
},
/**
* Removes the stored DOMMutation functions from the storage object
*
* @param string aHUDId
* @returns void
*/
removeStoredMutationFuncs:
function CDL_removeStoredMutationFuncs(aHUDId)
{
try {
delete HUDService.mutationEventFunctions[aHUDId];
}
catch (ex) {
Cu.reportError(ex);
}
}
};
/**
* ConsoleUtils: a collection of globally used functions
*
*/
function ConsoleUtils()
{ }
ConsoleUtils.prototype = {
/**
* Generates a millisecond resolution timestamp for console messages
*
* @returns string
*/
timeStamp: function ConsoleUtils_timeStamp()
{
function logDateString(d){
function pad(n, mil){
if (mil) {
return n < 100 ? '0'+n : n;
}
return n < 10 ? '0'+n : n;
}
return pad(d.getHours())+':'
+ pad(d.getMinutes())+':'
+ pad(d.getSeconds()) + ":"
+ pad(d.getMilliseconds(), true);
}
return logDateString(new Date());
}
};
//////////////////////////////////////////////////////////////////////////
// HeadsUpDisplayUICommands
//////////////////////////////////////////////////////////////////////////
HeadsUpDisplayUICommands = {
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 hud = gBrowser.selectedTab.ownerDocument.getElementById(hudId);
if (hud) {
HUDService.deactivateHUDForContext(gBrowser.selectedTab);
}
else {
HUDService.activateHUDForContext(gBrowser.selectedTab);
}
},
toggleFilter: function UIC_toggleFilter(aButton) {
var filter = aButton.getAttribute("buttonType");
var hudId = aButton.getAttribute("hudId");
var state = HUDService.getFilterState(hudId, filter);
if (state) {
HUDService.setFilterState(hudId, filter, false);
aButton.setAttribute("checked", false);
}
else {
HUDService.setFilterState(hudId, filter, true);
aButton.setAttribute("checked", true);
}
},
command: function UIC_command(aButton) {
var filter = aButton.getAttribute("buttonType");
var hudId = aButton.getAttribute("hudId");
if (filter == "clear") {
HUDService.clearDisplay(hudId);
}
},
toggleMutationListeners: function UIC_toggleMutationListeners(aButton)
{
var hudId = aButton.getAttribute("hudId");
// if the button is for mutations, tell HUD to toggle it
HUDService.toggleMutationListeners(hudId);
},
};
//////////////////////////////////////////////////////////////////////////
// ConsoleStorage
//////////////////////////////////////////////////////////////////////////
var prefs = Services.prefs;
const GLOBAL_STORAGE_INDEX_ID = "GLOBAL_CONSOLE";
const PREFS_BRANCH_PREF = "devtools.hud.display.filter";
const PREFS_PREFIX = "devtools.hud.display.filter.";
const PREFS = { mutation: PREFS_PREFIX + "mutation",
network: PREFS_PREFIX + "network",
cssparser: PREFS_PREFIX + "cssparser",
exception: PREFS_PREFIX + "exception",
error: PREFS_PREFIX + "error",
info: PREFS_PREFIX + "info",
warn: PREFS_PREFIX + "warn",
log: PREFS_PREFIX + "log",
global: PREFS_PREFIX + "global",
};
function ConsoleStorage()
{
this.sequencer = null;
this.consoleDisplays = {};
// each display will have an index that tracks each ConsoleEntry
this.displayIndexes = {};
this.globalStorageIndex = [];
this.globalDisplay = {};
this.createDisplay(GLOBAL_STORAGE_INDEX_ID);
// TODO: need to create a method that truncates the message
// see bug 570543
// store an index of display prefs
this.displayPrefs = {};
// check prefs for existence, create & load if absent, load them if present
let filterPrefs;
let defaultDisplayPrefs;
try {
filterPrefs = prefs.getBoolPref(PREFS_BRANCH_PREF);
}
catch (ex) {
filterPrefs = false;
}
// TODO: for BETA, use the sitePreferencesService to save specific site prefs
// see bug 570545
if (filterPrefs) {
defaultDisplayPrefs = {
mutation: (prefs.getBoolPref(PREFS.mutation) ? true: false),
network: (prefs.getBoolPref(PREFS.network) ? true: false),
cssparser: (prefs.getBoolPref(PREFS.cssparser) ? true: false),
exception: (prefs.getBoolPref(PREFS.exception) ? true: false),
error: (prefs.getBoolPref(PREFS.error) ? true: false),
info: (prefs.getBoolPref(PREFS.info) ? true: false),
warn: (prefs.getBoolPref(PREFS.warn) ? true: false),
log: (prefs.getBoolPref(PREFS.log) ? true: false),
global: (prefs.getBoolPref(PREFS.global) ? true: false),
};
}
else {
prefs.setBoolPref(PREFS_BRANCH_PREF, false);
// default prefs for each HeadsUpDisplay
prefs.setBoolPref(PREFS.mutation, false);
prefs.setBoolPref(PREFS.network, true);
prefs.setBoolPref(PREFS.cssparser, true);
prefs.setBoolPref(PREFS.exception, true);
prefs.setBoolPref(PREFS.error, true);
prefs.setBoolPref(PREFS.info, true);
prefs.setBoolPref(PREFS.warn, true);
prefs.setBoolPref(PREFS.log, true);
prefs.setBoolPref(PREFS.global, false);
defaultDisplayPrefs = {
mutation: prefs.getBoolPref(PREFS.mutation),
network: prefs.getBoolPref(PREFS.network),
cssparser: prefs.getBoolPref(PREFS.cssparser),
exception: prefs.getBoolPref(PREFS.exception),
error: prefs.getBoolPref(PREFS.error),
info: prefs.getBoolPref(PREFS.info),
warn: prefs.getBoolPref(PREFS.warn),
log: prefs.getBoolPref(PREFS.log),
global: prefs.getBoolPref(PREFS.global),
};
}
this.defaultDisplayPrefs = defaultDisplayPrefs;
}
ConsoleStorage.prototype = {
updateDefaultDisplayPrefs:
function CS_updateDefaultDisplayPrefs(aPrefsObject) {
prefs.setBoolPref(PREFS.mutation, (aPrefsObject.mutation ? true : false));
prefs.setBoolPref(PREFS.network, (aPrefsObject.network ? true : false));
prefs.setBoolPref(PREFS.cssparser, (aPrefsObject.cssparser ? true : false));
prefs.setBoolPref(PREFS.exception, (aPrefsObject.exception ? true : false));
prefs.setBoolPref(PREFS.error, (aPrefsObject.error ? true : false));
prefs.setBoolPref(PREFS.info, (aPrefsObject.info ? true : false));
prefs.setBoolPref(PREFS.warn, (aPrefsObject.warn ? true : false));
prefs.setBoolPref(PREFS.log, (aPrefsObject.log ? true : false));
prefs.setBoolPref(PREFS.global, (aPrefsObject.global ? true : false));
},
sequenceId: function CS_sequencerId()
{
if (!this.sequencer) {
this.sequencer = this.createSequencer();
}
return this.sequencer.next();
},
createSequencer: function CS_createSequencer()
{
function sequencer(aInt) {
while(1) {
aInt++;
yield aInt;
}
}
return sequencer(-1);
},
globalStore: function CS_globalStore(aIndex)
{
return this.displayStore(GLOBAL_CONSOLE_DOM_NODE_ID);
},
displayStore: function CS_displayStore(aId)
{
var self = this;
var idx = -1;
var id = aId;
var aLength = self.displayIndexes[id].length;
function displayStoreGenerator(aInt, aLength)
{
// create a generator object to iterate through any of the display stores
// from any index-starting-point
while(1) {
// throw if we exceed the length of displayIndexes?
aInt++;
var indexIt = self.displayIndexes[id];
var index = indexIt[aInt];
if (aLength < aInt) {
// try to see if we have more entries:
var newLength = self.displayIndexes[id].length;
if (newLength > aLength) {
aLength = newLength;
}
else {
throw new StopIteration();
}
}
var entry = self.consoleDisplays[id][index];
yield entry;
}
}
return displayStoreGenerator(-1, aLength);
},
recordEntries: function CS_recordEntries(aHUDId, aConfigArray)
{
var len = aConfigArray.length;
for (var i = 0; i < len; i++){
this.recordEntry(aHUDId, aConfigArray[i]);
}
},
recordEntry: function CS_recordEntry(aHUDId, aConfig)
{
var id = this.sequenceId();
this.globalStorageIndex[id] = { hudId: aHUDId };
var displayStorage = this.consoleDisplays[aHUDId];
var displayIndex = this.displayIndexes[aHUDId];
if (displayStorage && displayIndex) {
var entry = new ConsoleEntry(aConfig, id);
displayIndex.push(entry.id);
displayStorage[entry.id] = entry;
return entry;
}
else {
throw new Error("Cannot get displayStorage or index object for id " + aHUDId);
}
},
getEntry: function CS_getEntry(aId)
{
try {
var display = this.globalStorageIndex[aId];
var storName = display.hudId;
return this.consoleDisplays[storName][aId];
}
catch (ex) {
throw new Error("No entry with id: " + aId );
}
},
updateEntry: function CS_updateEntry(aUUID)
{
// update an individual entry
// TODO: see bug 568634
},
createDisplay: function CS_createdisplay(aId)
{
if(this.consoleDisplays[aId]) {
return;
}
else {
this.consoleDisplays[aId] = {};
this.displayIndexes[aId] = [];
}
},
removeDisplay: function CS_removeDisplay(aId)
{
try {
delete this.consoleDisplays[aId];
delete this.displayIndexes[aId];
}
catch (ex) {
Cu.reportError("Could not remove console display for id " + aId);
}
}
};
/**
* A Console log entry
*
* @param JSObject aConfig, object literal with ConsolEntry properties
* @param integer aId
* @returns void
*/
function ConsoleEntry(aConfig, id)
{
if (!aConfig.logLevel && aConfig.message) {
throw new Error("Missing Arguments when creating a console entry");
}
this.config = aConfig;
this.id = id;
for (var prop in aConfig) {
if (!(typeof aConfig[prop] == "function")){
this[prop] = aConfig[prop];
}
}
if (aConfig.logLevel == "network") {
this.transactions = { };
if (aConfig.activity) {
this.transactions[aConfig.activity.stage] = aConfig.activity;
}
}
}
ConsoleEntry.prototype = {
updateTransaction: function CE_updateTransaction(aActivity) {
this.transactions[aActivity.stage] = aActivity;
}
};
//////////////////////////////////////////////////////////////////////////
// HUDWindowObserver
//////////////////////////////////////////////////////////////////////////
let DEVTOOLS_HUD_ENABLED = false;
let HUD_PREF_BRANCH = "devtools.hud.";
let DEVTOOLS_HUD_IS_ENABLED = "devtools.hud.enabled";
function HUDPrefListener(branchName, func)
{
var branch = Services.prefs.getBranch(branchName);
branch.QueryInterface(Ci.nsIPrefBranch2);
this.register = function()
{
branch.addObserver("", this, false);
branch.getChildList("", { }).forEach(function (name){ func(branch, name);});
};
this.unregister = function unregister()
{
if (branch) {
branch.removeObserver("", this);
}
};
this.observe = function(subject, topic, data)
{
if (topic == "nsPref:changed") {
func(branch, data);
}
};
}
let HUDListener = new HUDPrefListener(HUD_PREF_BRANCH,
function(branch, name)
{
if (name == "enabled") {
// enabled pref was changed, need to start or stop the hudservice
let enabled = Services.prefs.getBoolPref("devtools.hud.enabled");
if (enabled) {
// make sure we addObserver for new contentWindows
Services.obs.addObserver(HUDWindowObserver, "xpcom-shutdown", false);
Services.obs.addObserver(HUDWindowObserver,
"content-document-global-created",
false);
}
else {
// need to stop window.console object injection
Services.obs.removeObserver(HUDWindowObserver,
"content-document-global-created",
false);
}
}
}
);
/////////////////////////////////////////////////////////////////////////////
// HeadsUpDisplay Initialization Object
/////////////////////////////////////////////////////////////////////////////
/**
* Initializer - checks what Gecko app is running and inits the HUD
*
* @param nsIDOMWindow aContentWindow
* @returns void
*/
function Initializer(aContentWindow)
{
// check for supported application:
var name;
var self = this;
name = appName();
switch (name) {
case "FIREFOX":
var xulWindow = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
this.activateConsoleFirefox(aContentWindow, xulWindow);
break;
case "FENNEC":
case "THUNDERBIRD":
case "SEAMONKEY":
default:
throw new Error("Unsupported Gecko Application");
}
}
Initializer.prototype = {
/**
* Firefox-specific console attachment method
*
* @param nsIDOMWindow aContentWindow
* @param nsIDOMWindow aXULWindow
* @returns void
*/
activateConsoleFirefox:
function Init_activateConsoleFirefox(aContentWindow, aXULWindow)
{
if (aContentWindow.document.location.href == "about:blank" &&
HUDWindowObserver.initialConsoleCreated == false) {
// TODO: need to make this work with about:blank in the future
// see bug 568661
return;
}
if (!gBrowser) {
let gBrowser = aXULWindow.gBrowser;
}
if (gBrowser && !HUDWindowObserver.initialConsoleCreated) {
HUDWindowObserver.initialConsoleCreated = true;
}
let gBrowser = aXULWindow.document.getElementById("content").wrappedJSObject;
let _browser =
gBrowser.getBrowserForDocument(aContentWindow.document.wrappedJSObject);
let tab = gBrowser.getNotificationBox(_browser);
let tabId = tab.getAttribute("id");
let hudId = "hud_" + tabId;
if (!HUDService.canActivateContext(hudId)) {
return;
}
HUDService.registerDisplay(hudId, aContentWindow.document.location.href);
// check if aContentWindow has a console Object
let _console = aContentWindow.wrappedJSObject.console;
if (!_console) {
// no console exists. does the HUD exist?
let hudNode;
let childNodes = tab.childNodes;
for (var i = 0; i < childNodes.length; i++) {
let id = childNodes[i].getAttribute("id");
if (id.split("_")[0] == "hud") {
hudNode = childNodes[i];
break;
}
}
if (!hudNode) {
// get tab object and call new HUD
let config = { parentNode: tab,
contentWindow: aContentWindow
};
let _hud = new HeadsUpDisplay(config);
}
else {
// only need to attach a console object to the window object
let config = { hudNode: hudNode,
consoleOnly: true,
contentWindow: aContentWindow
};
let _hud = new HeadsUpDisplay(config);
aContentWindow.wrappedJSObject.console = _hud.console;
var mutationFlag = HUDService.getFilterState(this.hudId, "mutation");
if (mutationFlag) {
HUDService.attachMutationListeners(aContentWindow, this.hudId);
}
}
}
// capture JS Errors
HUDService.setOnErrorHandler(aContentWindow);
},
};
HUDWindowObserver = {
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIObserver,]
),
init: function HWO_init()
{
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "content-document-global-created", false);
},
observe: function HWO_observe(aSubject, aTopic, aData)
{
if (aTopic == "content-document-global-created") {
new Initializer(aSubject);
}
else if (aTopic == "xpcom-shutdown") {
HUDService.shutdown();
}
},
/**
* once an initial console is created set this to true so we don't
* over initialize
*/
initialConsoleCreated: false,
};
///////////////////////////////////////////////////////////////////////////////
// HUDConsoleObserver
///////////////////////////////////////////////////////////////////////////////
/**
* HUDConsoleObserver: Observes nsIConsoleService for global consoleMessages,
* if a message originates inside a contentWindow we are tracking,
* then route that message to the HUDService for logging.
*/
HUDConsoleObserver = {
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIObserver]
),
init: function HCO_init()
{
Services.obs.addObserver(this,
"content-document-global-created", false);
Services.console.registerListener(this);
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
observe: function HCO_observe(aSubject, aTopic, aData)
{
if (aTopic == "xpcom-shutdown") {
Services.console.unregisterListener(this);
}
if (aSubject instanceof Ci.nsIConsoleMessage) {
var err = aSubject.QueryInterface(Ci.nsIScriptError);
switch (err.category) {
case "XPConnect JavaScript":
case "component javascript":
case "chrome javascript":
// we ignore these CHROME-originating errors as we only
// care about content
return;
case "HUDConsole":
case "CSS Parser":
case "content javascript":
HUDService.reportConsoleServiceContentScriptError(err);
return;
default:
HUDService.reportConsoleServiceMessage(aSubject);
return;
}
}
}
};
///////////////////////////////////////////////////////////////////////////
// appName
///////////////////////////////////////////////////////////////////////////
/**
* Get the app's name so we can properly dispatch app-specific
* methods per API call
* @returns Gecko application name
*/
function appName()
{
let APP_ID = Services.appinfo.QueryInterface(Ci.nsIXULRuntime).ID;
let APP_ID_TABLE = {
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "FIREFOX" ,
"{3550f703-e582-4d05-9a08-453d09bdfdc6}": "THUNDERBIRD",
"{a23983c0-fd0e-11dc-95ff-0800200c9a66}": "FENNEC" ,
"{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SEAMONKEY",
};
let name = APP_ID_TABLE[APP_ID];
if (name){
return name;
}
throw new Error("appName: UNSUPPORTED APPLICATION UUID");
}
//////////////////////////////////////////////////////////////////////////////
// Utility functions used by multiple callers
//////////////////////////////////////////////////////////////////////////////
/**
* Hides a log message by changing its class
*
* @param nsIDOMNode aMessageNode
* @returns nsIDOMNode
*/
function hideLogMessage(aMessageNode) {
var klass = aMessageNode.getAttribute("class");
klass += " hud-hidden";
aMessageNode.setAttribute("class", klass);
return aMessageNode;
}
///////////////////////////////////////////////////////////////////////////
// HUDService (exported symbol)
///////////////////////////////////////////////////////////////////////////
try {
// start the HUDService
// This is in a try block because we want to kill everything if
// *any* of this fails
var HUDService = new HUD_SERVICE();
HUDWindowObserver.init();
HUDConsoleObserver.init();
}
catch (ex) {
Cu.reportError("HUDService failed initialization.");
Cu.reportError(ex);
// TODO: kill anything that may have started up
// see bug 568665
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment