Diving into the Source Frames in Chrome DevTools

Chrome(10y ago)

#Javascript Editor

The javascript editor in devtools parlance is refereed to as a Source Frame. There are two source frames in devtools, the javascript editor and css editor.

The javascript editor not surprisingly has a custom logic for communicating with the console and handling breakpoints.

The main source frame, is more concerned with showing content, searching, jumping to lines.

The editor is in many ways a glorified wrapper in that a lot of the actual work is handled by the text editor (codemirror). For example, when the editor wants to jump to a nother search result it tells the editor to highlight the result.

jumpToSearchResult: function(index) {
this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);

var result = this._searchResults[this._currentSearchResultIndex]
this._textEditor.highlightSearchResults(this._searchRegex, result);
}
jumpToSearchResult: function(index) {
this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
this._currentSearchMatchChangedCallback(this._currentSearchResultIndex);

var result = this._searchResults[this._currentSearchResultIndex]
this._textEditor.highlightSearchResults(this._searchRegex, result);
}

#Shortcuts

All of the shortcuts are defined in an object WebInspector.ShortcutsScreen.SourcesPanelShortcuts. Here's a dump of them for fun:

  • SelectNextOccurrence
  • SoftUndo
  • GotoMatchingBracket
  • ToggleAutocompletion
  • IncreaseCSSUnitByOne
  • DecreaseCSSUnitByOne
  • IncreaseCSSUnitByTen
  • DecreaseCSSUnitByTen
  • EvaluateSelectionInConsole
  • AddSelectionToWatch
  • GoToMember
  • GoToLine
  • ToggleBreakpoint
  • NextCallFrame
  • PrevCallFrame
  • ToggleComment
  • JumpToPreviousLocation
  • JumpToNextLocation
  • CloseEditorTab
  • Save
  • SaveAll

The function link in the object popover is a little know trick for jumping to a function definition.

Diving into the source frames

Not surprisingly, the link is setup inside the ObjectHelper

When you hover over a function showObjectPopover is called. That then kicks off a _queryObject fetch. Then, if the result is a function, the result is re-fetched for its properties result.getOwnProperties and shown didGetFunctionProperties.

The formatting for the popover is done in didGetFunctionDetails

function didGetFunctionDetails(popoverContentElement, anchorElement, response) {
var container = createElementWithClass("div", "object-popover-container");

var title = container.createChild(
"div",
"function-popover-title source-code"
);
var functionName = (title.createChild("span", "function-name").textContent =
response.functionName);

var link = this._lazyLinkifier().linkifyRawLocation(
response.location,
response.sourceURL,
"function-location-link"
);
title.appendChild(link);

container.appendChild(popoverContentElement);
popover.showForAnchor(container, anchorElement);
}
function didGetFunctionDetails(popoverContentElement, anchorElement, response) {
var container = createElementWithClass("div", "object-popover-container");

var title = container.createChild(
"div",
"function-popover-title source-code"
);
var functionName = (title.createChild("span", "function-name").textContent =
response.functionName);

var link = this._lazyLinkifier().linkifyRawLocation(
response.location,
response.sourceURL,
"function-location-link"
);
title.appendChild(link);

container.appendChild(popoverContentElement);
popover.showForAnchor(container, anchorElement);
}

I think it's pretty cool that the popover is shown with showForAnchor w/ the anchor element being passed in.

#Linkifier

The ObjectHelper sets up a basic Linkifier for jumping to function definitions.

WebInspector.Linkifier = function (formatter) {
this._formatter =
formatter ||
new WebInspector.Linkifier.DefaultFormatter(
WebInspector.Linkifier.MaxLengthForDisplayedURLs
);
this._liveLocationsByTarget = new Map();
WebInspector.targetManager.observeTargets(this);
};
WebInspector.Linkifier = function (formatter) {
this._formatter =
formatter ||
new WebInspector.Linkifier.DefaultFormatter(
WebInspector.Linkifier.MaxLengthForDisplayedURLs
);
this._liveLocationsByTarget = new Map();
WebInspector.targetManager.observeTargets(this);
};

When building a link, it uses the standard Linkifier instance method linkifyRawLocation.

linkifyRawLocation: function(rawLocation, fallbackUrl, classes) {
return this.linkifyScriptLocation(
rawLocation.target(),
rawLocation.scriptId,
fallbackUrl,
rawLocation.lineNumber,
rawLocation.columnNumber,
classes // these will be the <a> classses
);
}


linkifyScriptLocation: function(target, scriptId, sourceURL, lineNumber, columnNumber, classes) {
var fallbackAnchor = WebInspector.linkifyResourceAsNode(sourceURL, lineNumber, classes);
var rawLocation = scriptId ?
target.debuggerModel.createRawLocationByScriptId(scriptId, lineNumber, columnNumber || 0) :
target.debuggerModel.createRawLocationByURL(sourceURL, lineNumber, columnNumber || 0);

var anchor = this._createAnchor(classes);
var liveLocation = WebInspector.debuggerWorkspaceBinding.createLiveLocation(
rawLocation, this._updateAnchor.bind(this, anchor));

this._liveLocationsByTarget.get(rawLocation.target()).set(anchor, liveLocation);

anchor[WebInspector.Linkifier._fallbackAnchorSymbol] = fallbackAnchor;
return anchor;
},
linkifyRawLocation: function(rawLocation, fallbackUrl, classes) {
return this.linkifyScriptLocation(
rawLocation.target(),
rawLocation.scriptId,
fallbackUrl,
rawLocation.lineNumber,
rawLocation.columnNumber,
classes // these will be the <a> classses
);
}


linkifyScriptLocation: function(target, scriptId, sourceURL, lineNumber, columnNumber, classes) {
var fallbackAnchor = WebInspector.linkifyResourceAsNode(sourceURL, lineNumber, classes);
var rawLocation = scriptId ?
target.debuggerModel.createRawLocationByScriptId(scriptId, lineNumber, columnNumber || 0) :
target.debuggerModel.createRawLocationByURL(sourceURL, lineNumber, columnNumber || 0);

var anchor = this._createAnchor(classes);
var liveLocation = WebInspector.debuggerWorkspaceBinding.createLiveLocation(
rawLocation, this._updateAnchor.bind(this, anchor));

this._liveLocationsByTarget.get(rawLocation.target()).set(anchor, liveLocation);

anchor[WebInspector.Linkifier._fallbackAnchorSymbol] = fallbackAnchor;
return anchor;
},