Skip to content

Commit

Permalink
[embedlite-components] Print channel info to the logs. Contributes to…
Browse files Browse the repository at this point in the history
… JB#50424

Provides information about channels when opening a website. When active
the following info is output to the log:

1. Request type and URL.
2. Response status.
3. Request and response headers.
4. A dump of the returned content if it's textual.

The aim is to provide something a little similar to the Network tab of
the developer tools in Firefox.

The mode is activated by including the string "network" in the
EMBED_CONSOLE environment variable. This can be combined with others,
for example:

EMBED_CONSOLE="stacktrace,network" sailfish-browser
  • Loading branch information
llewelld committed Nov 10, 2020
1 parent 762b37a commit d047abf
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 1 deletion.
110 changes: 110 additions & 0 deletions jscomps/EmbedLiteConsoleListener.js
Expand Up @@ -57,6 +57,107 @@ SPConsoleListener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener])
};

// Captures the data received on a channel for debug output
// See https://developer.mozilla.org/en-US/docs/Mozilla/Creating_sandboxed_HTTP_connections
// and http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
function DocumentContentListener(aHttpChannel) {
this.originalListener = null;
this.receivedData = [];
this.httpChannel = aHttpChannel;
this.maxDebugPrint = 32 * 1024;
}

DocumentContentListener.prototype = {
onDataAvailable: function(request, context, inputStream, offset, count) {
var binaryInputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci["nsIBinaryInputStream"]);
var storageStream = Cc["@mozilla.org/storagestream;1"].createInstance(Ci["nsIStorageStream"]);
var binaryOutputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci["nsIBinaryOutputStream"]);

binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));

// Copy received data as they come
var data = binaryInputStream.readBytes(count);
this.receivedData.push(data);

binaryOutputStream.writeBytes(data, count);

this.originalListener.onDataAvailable(request, context, storageStream.newInputStream(0), offset, count);
},

onStartRequest: function(request, context) {
this.originalListener.onStartRequest(request, context);
var visitor = new DebugHeaderVisitor()

// Output the headers
Logger.debug(" [ Request headers --------------------------------------- ]");
this.httpChannel.visitRequestHeaders(visitor);

Logger.debug(" [ Response headers -------------------------------------- ]");
this.httpChannel.visitOriginalResponseHeaders(visitor);
},

onStopRequest: function(request, context, statusCode) {
// Get entire response
var responseSource = this.receivedData.join("").substring(0, this.maxDebugPrint);
this.originalListener.onStopRequest(request, context, statusCode);

// Output the content (sometimes)
Logger.debug(" [ Document content -------------------------------------- ]");
if (this.httpChannel.contentCharset !== "") {
Logger.debug(responseSource);
if (this.httpChannel.decodedBodySize > this.maxDebugPrint) {
Logger.debug(" Document output truncated by", (this.httpChannel.decodedBodySize - this.maxDebugPrint),"bytes");
}
} else {
Logger.debug(" Document output skipped, content-type non-text or unknown");
}
Logger.debug(" [ Document content ends --------------------------------- ]");
},

QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
}
}

// Used to cycle through all the headers
function DebugHeaderVisitor() {
}

DebugHeaderVisitor.prototype.visitHeader = function (aHeader, aValue) {
Logger.debug(" ", aHeader, ":", aValue);
};

// Sets up the channel for debug output
function LogChannelInfo(aSubject) {
var httpChannel = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
if (httpChannel) {
var responseStatus = 0;
var responseStatusText = "";
var requestMethod = "unknown";
try {
responseStatus = httpChannel.responseStatus;
responseStatusText = httpChannel.responseStatusText;
requestMethod = httpChannel.requestMethod;
} catch (e) {}
Logger.debug("[ Request details ------------------------------------------- ]");
Logger.debug(" Request:", requestMethod, "status:", responseStatus, responseStatusText);
Logger.debug(" URL:", httpChannel.URI.spec);

// At this point the headers and content-type may not be valid, for example if
// the document is coming from the cache; they'll become available from the
// listener's onStartRequest callback. See gecko bug 489317.
var newListener = new DocumentContentListener(httpChannel);
aSubject.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = aSubject.setNewListener(newListener);
}
}

function EmbedLiteConsoleListener()
{
}
Expand Down Expand Up @@ -85,6 +186,15 @@ EmbedLiteConsoleListener.prototype = {
Services.console.registerListener(this._listener);
Services.obs.addObserver(this, "embedui:logger", true);
}

if (Logger.devModeNetworkEnabled) {
Services.obs.addObserver(this, 'http-on-examine-response', false);
}

break;
}
case "http-on-examine-response": {
LogChannelInfo(aSubject);
break;
}
case "embedui:logger": {
Expand Down
6 changes: 5 additions & 1 deletion jsscripts/Logger.js
Expand Up @@ -25,7 +25,11 @@ let Logger = {
},

get stackTraceEnabled() {
return this._consoleEnv === "stacktrace";
return this._consoleEnv.indexOf("stacktrace") !== -1;
},

get devModeNetworkEnabled() {
return this._consoleEnv.indexOf("network") !== -1;
},

get enabled() {
Expand Down

0 comments on commit d047abf

Please sign in to comment.