Created
June 17, 2010 17:40
-
-
Save daviddahl/442447 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*- 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