How does DevTools show the functions popup?
Member List / Symbol List / Function List
When cmd+shift+o
or cmd+shift+p
is pressed the callback
SourcesView._showOutlineDialog
is fired.
_showOutlineDialog: function(event) {
var uiSourceCode = this._editorContainer.currentFile();
WebInspector.JavaScriptOutlineDialog.show(this, uiSourceCode, this.showSourceLocation);
}
the uiSourceCode
property is interesting. It’s type is WebInspector.UiSourceCode
properties include:
- _project: => WebInspector.Project
- _parentPath: => ‘’
- _name
- _originURL => “http://localhost:3000/bundle.js”
- _url => “http://localhost:3000/bundle.js”
- _contentType => ResourceType … basically just “Script”
- _requestContentCallbacks
- history => actions
- _listeners
- _contentLoaded
- _content => “(function e(t,n,r){fun…” basically all the text
- Symbol(target) => I think it’s used for resolving lookups
- Symbol(NetworkContentType) => I think it’s used for resolving lookups
JavaScriptOutlineDialog
The JavaScriptOutlineDialog
is the object responsible for showing the list of functions.
Of course, finding the function definitions for an arbitrary string of javascript is not easy work. This is why the dialog delegates to a web worker called _outlineWorker
.
WebInspector.JavaScriptOutlineDialog = function(uiSourceCode, selectItemCallback) {
WebInspector.SelectionDialogContentProvider.call(this);
this._functionItems = [];
this._selectItemCallback = selectItemCallback;
this._outlineWorker = new WorkerRuntime.Worker("script_formatter_worker");
this._outlineWorker.onmessage = this._didBuildOutlineChunk.bind(this);
this._outlineWorker.postMessage({
method: "javaScriptOutline",
params: { content: uiSourceCode.workingCopy() }
});
}
script_formatter_worker
The worker is setup here script_formatter_worker
and will respond to messages with a method javaScriptOutline
. When the worker is done it’ll call the dialog’s _didBuildOutlineChunk
callback.
I’m not going to go into too much depth on how the worker builds the list of function identifiers, but here’s a quick sneak peak. The important things to note are:
- it uses a javascript tokenizer
- it tokenizes line by line
- the tokenizer calls a processToken callback which looks for functions
FormatterWorker.javaScriptOutline = function(params)
{
var lines = params.content.split("\n");
var tokenizer = FormatterWorker.createTokenizer("text/javascript");
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
tokenizer(line, processToken);
}
// look for either:
// A named function: "function f...".
// Anonymous function assigned to an identifier: "...f = function..."
// or "funcName: function...".
function processToken(tokenValue, tokenType, column, newColumn) {
if (isJavaScriptIdentifier(tokenType)) {
return { line: i, column: column, name: tokenValue };
} else if (tokenType === "keyword" && tokenValue === "function" &&
(previousToken === "=" || previousToken === ":")) {
return { line: i, column: column, name: previousIdentifier };
}
}
postMessage({ chunk: outlineChunk, isLastChunk: true });
}
_didBuildOutlineChunk
When the worker is done with some of the work, it calls the javascript dialog’s callback _didBuildOutlineChunk
.
the callback is passed in an event, with a data param that has a list of chunks. The chunks are our function definitions!
we do not have function bodies here, which is kind of a bummer.
[
{
"arguments": "(fragment, forcePushState)",
"column": 15,
"line": 75,
"name": "getFragment"
}
]
Chunk is used liberally here because the worker is smart enough to batch it’s work. If for example, it’s parsing a massive javascript file it might do the first thousand lines, return the function definitions and then continue.
Filtering the list
When the list comes back, there are three things to consider: filtering, sorting, updating the list.
- filtering will be done in the search field on the top
- scoring is a complicated algorithm, which determines which functions to show first
_filterItems: function() {
this._query = this.rewriteQuery(this._promptElement.value.trim());
scoreItems.call(this, 0);
}