Firefox DevTools - Showing a File
How are sources shown in the debugger?
In this post, we’re going to trace the steps for showing a source file in the debugger. To do this, we should first look at two components of the debugger: the Editor and the Sources pane. The Editor is the component responsible for showing a source’s file. In the case of Firefox, it’s a CodeMirror editor. Chrome DevTools and many other popular tools use CodeMirror. The Sources component is in the left sidebar and shows a file list or tree of all the sources.
The Beginning: Setting up the Sources Component
The Sources component is a widget that’s setup and managed by the SourcesView
.
this.widget = new SideMenuWidget(document.getElementById("sources"), {
contextMenu: document.getElementById("debuggerSourcesContextMenu"),
showArrows: true
});
The click
handler for selecting a source file is bound to the widget in
the SourcesView
initialization logic.
this.widget.addEventListener("select", this._onSourceSelect, false);
The Click: What happens when I select a source?
When a source is selected in the sidebar pane the _onSourceSelect
handler is called
with a sourceItem
parameter. The debugger follows Redux patterns, so our handler
can be very simple: essentially invoke the select source action.
_onSourceSelect: function({ detail: sourceItem }) {
this.actions.selectSource(sourceItem.attachment);
},
The sourceItem.attachment
property is a serializable representation of our source.
It’s important that it has an actor
id and other useful data about being blackboxed,
pretty printed, etc.
{
"label": "/source/src/books/index/route.js",
"group": "",
"checkboxState": true,
"checkboxTooltip": "Toggle black boxing",
"source": {
"actor": "server1.conn2.child1/60",
"generatedUrl": "http://www.marionettewires.com/bundle.js",
"url": "/source/src/books/index/route.js",
"isBlackBoxed": false,
"isPrettyPrinted": false,
"isSourceMapped": true,
"introductionUrl": null,
"introductionType": "scriptElement"
}
}
Action Time: Lets do something real
The selectSource
action does two things for us, it starts
loading the source text and it does the UI work of showing the selected source.
When the debugger is showing a source file for the first time, the process looks something like this. Start fetching the source, and while we’re waiting show a loading message. When we get the source back from the backend, go ahead and show it. There two corresponding events here: “source-selected” and “source-text-loaded”. We’ll get to these events in a minute.
function selectSource(source, opts) {
return (dispatch, getState) => {
source = getSource(getState(), source.actor);
dispatch(loadSourceText(source));
dispatch(selectSource(source));
};
}
The Debugger View does the heavy lifting
The DebuggerView
handles both events (“source-selected” and “source-text-loaded”).
They’re setup in this onReducerEvents
.
onReducerEvents(this.controller, {
"source-text-loaded": this.renderSourceText,
"source-selected": this.renderSourceText
}, this);
If you’re wondering what the DebuggerView is responsible for, the answer is a lot. Here is a list of components that are initialized when the DebuggerView starts up.
Panes
Editor
Toolbar
Options
Filtering
StackFrames
Workers
Sources
WatchExpressions
EventListeners
GlobalSearch
VariablesView
Render Time: How are sources shown?
Both events “source-text-loaded” and “source-selected” are handled by the same came
callback renderSourceText
, which boils down to four different scenarios for the source:
- it’s blackboxed
- it’s loading
- something went wrong
- it’s fine, and we should show it
function renderSourceText (source, textInfo) {
if (source.isBlackBoxed) {
this.showBlackBoxMessage();
} else if (textInfo.isLoading) {
this.showLoadingMessage();
} else if (textInfo.error) {
this.showErrorMessage(textInfo.error)
} else {
this.showSourceFile(source, textInfo)
}
}
In all four cases, we have some text that is ready to be shown in the editor. We show it by first creating a CodeMirror document for the source if one doesn’t exist, and then setting the text in the document. If we already have a document for the source, we assume it’s uptodate and show the document.
Overview
We’ve traced the codepath from clicking on a source in the sources sidebar to the text showing up in the editor. What have we learned:
1) There’s one click handler that kicks everything off with a Redux Action 2) There’s a Redux reducer that handles fetching the source and showing the source 3) The DebuggerView renders the text into the editor. It also does a hundred other things :)