Skip to content

Commit b57b603

Browse files
authored
Implement fetching JS sources from browser via WebChannel (#5506)
Backend bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1916785 (but patches are from multiple bugs, see https://phabricator.services.mozilla.com/D259273 for the whole stack) This PR adds support for fetching the JS source code via WebChannel and displaying them in the source view.
2 parents 2d76b88 + 9f0aefb commit b57b603

File tree

74 files changed

+20928
-18857
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+20928
-18857
lines changed

docs-developer/CHANGELOG-formats.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Note that this is not an exhaustive list. Processed profile format upgraders can
66

77
## Processed profile format
88

9+
### Version 58
10+
11+
A new `SourceTable` has been added to `profile.shared.sources` to centralize all source file information. The `FuncTable.fileName` field has been replaced with `FuncTable.source`, which references indices in the shared sources table. This change allows storing a UUID per JS source, which will be used for fetching sources.
12+
913
### Version 57
1014

1115
The `searchable` property in marker schemas, originally added in version 44, is now removed again. Now all marker fields are searchable.
@@ -122,6 +126,10 @@ Older versions are not documented in this changelog but can be found in [process
122126

123127
## Gecko profile format
124128

129+
### Version 32
130+
131+
`frameTable` `location` string field was changed to include an optional `sourceIndex` at the end of the string inside brackets for JS sources. For example, new JS frames look like this: `functionName (http://script.url/:1234:1234)[1234]` with the last number being its `sourceIndex`. This index references entries in the shared `SourceTable` in `profile.sources` (added in in the same version) which centralizes all source file information.
132+
125133
### Version 31
126134

127135
Two new marker schema field format types have been added: `flow-id` and `terminating-flow-id`, with string index values (like `unique-string`).

locales/en-US/app.ftl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,15 @@ SourceView--not-in-archive-error-when-obtaining-source =
12431243
SourceView--archive-parsing-error-when-obtaining-source =
12441244
The archive at { $url } could not be parsed: { $parsingErrorMessage }
12451245
1246+
# Displayed below SourceView--cannot-obtain-source, if a JS file could not be found in
1247+
# the browser.
1248+
# Variables:
1249+
# $url (String) - The URL of the JS source file.
1250+
# $sourceUuid (number) - The UUID of the JS source file.
1251+
# $errorMessage (String) - The raw internal error message, not localized
1252+
SourceView--not-in-browser-error-when-obtaining-js-source =
1253+
The browser was unable to obtain the source file for { $url } with sourceUuid { $sourceUuid }: { $errorMessage }.
1254+
12461255
## Toggle buttons in the top right corner of the bottom box
12471256

12481257
# The toggle button for the assembly view, while the assembly view is hidden.

src/actions/code.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,34 @@ import type {
66
SourceCodeLoadingError,
77
ApiQueryError,
88
DecodedInstruction,
9+
IndexIntoSourceTable,
910
} from 'firefox-profiler/types';
1011

1112
export function beginLoadingSourceCodeFromUrl(
12-
file: string,
13+
sourceIndex: IndexIntoSourceTable,
1314
url: string
1415
): Action {
15-
return { type: 'SOURCE_CODE_LOADING_BEGIN_URL', file, url };
16+
return { type: 'SOURCE_CODE_LOADING_BEGIN_URL', sourceIndex, url };
1617
}
1718

1819
export function beginLoadingSourceCodeFromBrowserConnection(
19-
file: string
20+
sourceIndex: IndexIntoSourceTable
2021
): Action {
21-
return { type: 'SOURCE_CODE_LOADING_BEGIN_BROWSER_CONNECTION', file };
22+
return { type: 'SOURCE_CODE_LOADING_BEGIN_BROWSER_CONNECTION', sourceIndex };
2223
}
2324

24-
export function finishLoadingSourceCode(file: string, code: string): Action {
25-
return { type: 'SOURCE_CODE_LOADING_SUCCESS', file, code };
25+
export function finishLoadingSourceCode(
26+
sourceIndex: IndexIntoSourceTable,
27+
code: string
28+
): Action {
29+
return { type: 'SOURCE_CODE_LOADING_SUCCESS', sourceIndex, code };
2630
}
2731

2832
export function failLoadingSourceCode(
29-
file: string,
33+
sourceIndex: IndexIntoSourceTable,
3034
errors: SourceCodeLoadingError[]
3135
): Action {
32-
return { type: 'SOURCE_CODE_LOADING_ERROR', file, errors };
36+
return { type: 'SOURCE_CODE_LOADING_ERROR', sourceIndex, errors };
3337
}
3438

3539
export function beginLoadingAssemblyCodeFromUrl(

src/actions/profile-view.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,7 +1917,7 @@ export function changeTableViewOptions(
19171917

19181918
export function updateBottomBoxContentsAndMaybeOpen(
19191919
currentTab: TabSlug,
1920-
{ libIndex, sourceFile, nativeSymbols }: BottomBoxInfo
1920+
{ libIndex, sourceIndex, nativeSymbols }: BottomBoxInfo
19211921
): Action {
19221922
// TODO: If the set has more than one element, pick the native symbol with
19231923
// the highest total sample count
@@ -1926,12 +1926,12 @@ export function updateBottomBoxContentsAndMaybeOpen(
19261926
return {
19271927
type: 'UPDATE_BOTTOM_BOX',
19281928
libIndex,
1929-
sourceFile,
1929+
sourceIndex,
19301930
nativeSymbol,
19311931
allNativeSymbolsForInitiatingCallNode: nativeSymbols,
19321932
currentTab,
1933-
shouldOpenBottomBox: sourceFile !== null || nativeSymbol !== null,
1934-
shouldOpenAssemblyView: sourceFile === null && nativeSymbol !== null,
1933+
shouldOpenBottomBox: sourceIndex !== null || nativeSymbol !== null,
1934+
shouldOpenAssemblyView: sourceIndex === null && nativeSymbol !== null,
19351935
};
19361936
}
19371937

src/app-logic/browser-connection.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
querySymbolicationApiViaWebChannel,
1313
getPageFaviconsViaWebChannel,
1414
showFunctionInDevtoolsViaWebChannel,
15+
getJSSourcesViaWebChannel,
1516
} from './web-channel';
1617
import type {
1718
Milliseconds,
@@ -83,6 +84,8 @@ export interface BrowserConnection {
8384
line: number | null,
8485
column: number | null
8586
): Promise<void>;
87+
88+
getJSSource(sourceUuid: string): Promise<string>;
8689
}
8790

8891
/**
@@ -98,6 +101,7 @@ class BrowserConnectionImpl implements BrowserConnection {
98101
_webChannelSupportsGetExternalMarkers: boolean;
99102
_webChannelSupportsGetPageFavicons: boolean;
100103
_webChannelSupportsOpenDebuggerInTab: boolean;
104+
_webChannelSupportsGetJSSource: boolean;
101105
_geckoProfiler: $GeckoProfiler | undefined;
102106

103107
constructor(webChannelVersion: number) {
@@ -106,6 +110,7 @@ class BrowserConnectionImpl implements BrowserConnection {
106110
this._webChannelSupportsGetExternalMarkers = webChannelVersion >= 3;
107111
this._webChannelSupportsGetPageFavicons = webChannelVersion >= 4;
108112
this._webChannelSupportsOpenDebuggerInTab = webChannelVersion >= 5;
113+
this._webChannelSupportsGetJSSource = webChannelVersion >= 6;
109114
}
110115

111116
// Only called when we must obtain the profile from the browser, i.e. if we
@@ -226,6 +231,31 @@ class BrowserConnectionImpl implements BrowserConnection {
226231

227232
return [];
228233
}
234+
235+
/**
236+
* Fetches JavaScript source code from the browser using the source UUID.
237+
* This method requires WebChannel version 6 or higher (Firefox 145+).
238+
*/
239+
async getJSSource(sourceUuid: string): Promise<string> {
240+
if (!this._webChannelSupportsGetJSSource) {
241+
throw new Error(
242+
"Can't use getJSSource in Firefox versions with the old WebChannel."
243+
);
244+
}
245+
246+
// Even though the WebChannel request for fetching JS sources supports
247+
// fetching multiple sources, we only fetch one at a time currently.
248+
// TODO: Change this to fetch multiple JS sources at the load time or while
249+
// we share the profile.
250+
return getJSSourcesViaWebChannel([sourceUuid]).then((sources) => {
251+
const source = sources[0];
252+
if ('error' in source) {
253+
throw new Error(source.error);
254+
}
255+
256+
return source.sourceText;
257+
});
258+
}
229259
}
230260

231261
// Should work with:

src/app-logic/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import type { MarkerPhase } from 'firefox-profiler/types';
77
// The current version of the Gecko profile format.
88
// Please don't forget to update the gecko profile format changelog in
99
// `docs-developer/CHANGELOG-formats.md`.
10-
export const GECKO_PROFILE_VERSION = 31;
10+
export const GECKO_PROFILE_VERSION = 32;
1111

1212
// The current version of the "processed" profile format.
1313
// Please don't forget to update the processed profile format changelog in
1414
// `docs-developer/CHANGELOG-formats.md`.
15-
export const PROCESSED_PROFILE_VERSION = 57;
15+
export const PROCESSED_PROFILE_VERSION = 58;
1616

1717
// The following are the margin sizes for the left and right of the timeline. Independent
1818
// components need to share these values.

src/app-logic/url-handling.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ import {
4747
encodeUintSetForUrlComponent,
4848
} from '../utils/uintarray-encoding';
4949
import { tabSlugs } from '../app-logic/tabs-handling';
50+
import { StringTable } from 'firefox-profiler/utils/string-table';
5051

51-
export const CURRENT_URL_VERSION = 11;
52+
export const CURRENT_URL_VERSION = 12;
5253

5354
/**
5455
* This static piece of state might look like an anti-pattern, but it's a relatively
@@ -171,7 +172,7 @@ type BaseQuery = {
171172
view: string;
172173
implementation: string;
173174
timelineType: string;
174-
sourceView: string;
175+
sourceViewIndex: number;
175176
assemblyView: string;
176177
};
177178

@@ -208,7 +209,7 @@ type Query = BaseQuery & {
208209
invertCallstack?: null | undefined;
209210
ctSummary?: string;
210211
transforms?: string;
211-
sourceView?: string;
212+
sourceViewIndex?: number;
212213
assemblyView?: string;
213214

214215
// StackChart specific
@@ -349,8 +350,8 @@ export function getQueryStringFromUrlState(urlState: UrlState): string {
349350
urlState.profileSpecific;
350351

351352
if (isBottomBoxOpenPerPanel[selectedTab]) {
352-
if (sourceView.sourceFile !== null) {
353-
query.sourceView = sourceView.sourceFile;
353+
if (sourceView.sourceIndex !== null) {
354+
query.sourceViewIndex = sourceView.sourceIndex;
354355
}
355356
if (assemblyView.isOpen && assemblyView.nativeSymbol !== null) {
356357
query.assemblyView = stringifyAssemblyViewSymbol(
@@ -508,7 +509,7 @@ export function stateFromLocation(
508509
const sourceView: SourceViewState = {
509510
scrollGeneration: 0,
510511
libIndex: null,
511-
sourceFile: null,
512+
sourceIndex: null,
512513
};
513514
const assemblyView: AssemblyViewState = {
514515
isOpen: false,
@@ -518,8 +519,8 @@ export function stateFromLocation(
518519
};
519520
const isBottomBoxOpenPerPanel: any = {};
520521
tabSlugs.forEach((tabSlug) => (isBottomBoxOpenPerPanel[tabSlug] = false));
521-
if (query.sourceView) {
522-
sourceView.sourceFile = query.sourceView;
522+
if (query.sourceViewIndex !== undefined) {
523+
sourceView.sourceIndex = Number(query.sourceViewIndex);
523524
isBottomBoxOpenPerPanel[selectedTab] = true;
524525
}
525526
if (query.assemblyView) {
@@ -1162,6 +1163,35 @@ const _upgraders: {
11621163
processedLocation.query = {};
11631164
}
11641165
},
1166+
[12]: (
1167+
processedLocation: ProcessedLocationBeforeUpgrade,
1168+
profile?: Profile
1169+
) => {
1170+
// This version changes the source view parameter from 'sourceView' (filename
1171+
// string) to 'sourceViewIndex' (IndexIntoSourceTable). If we can't convert
1172+
// the filename to a source index, we just remove the sourceView parameter.
1173+
const { query } = processedLocation;
1174+
if (!('sourceView' in query) || !profile || !profile.shared.sources) {
1175+
return;
1176+
}
1177+
1178+
// Try to find the source index for the given filename
1179+
const filename = query.sourceView;
1180+
const { sources, stringArray } = profile.shared;
1181+
const stringTable = StringTable.withBackingArray(stringArray);
1182+
1183+
// Find the filename string index
1184+
const filenameStringIndex = stringTable.indexForString(filename);
1185+
if (filenameStringIndex !== -1) {
1186+
// Find the source index with this filename
1187+
const sourceIndex = sources.filename.indexOf(filenameStringIndex);
1188+
if (sourceIndex !== -1) {
1189+
query.sourceViewIndex = sourceIndex;
1190+
}
1191+
}
1192+
// Remove the old sourceView parameter regardless of whether we found a match
1193+
delete query.sourceView;
1194+
},
11651195
};
11661196

11671197
for (let destVersion = 1; destVersion <= CURRENT_URL_VERSION; destVersion++) {

src/app-logic/web-channel.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export type Request =
2828
| GetSymbolTableRequest
2929
| QuerySymbolicationApiRequest
3030
| GetPageFaviconsRequest
31-
| OpenScriptInTabDebuggerRequest;
31+
| OpenScriptInTabDebuggerRequest
32+
| GetJSSourcesRequest;
3233

3334
type StatusQueryRequest = { type: 'STATUS_QUERY' };
3435
type EnableMenuButtonRequest = { type: 'ENABLE_MENU_BUTTON' };
@@ -64,6 +65,10 @@ type OpenScriptInTabDebuggerRequest = {
6465
line: number | null;
6566
column: number | null;
6667
};
68+
type GetJSSourcesRequest = {
69+
type: 'GET_JS_SOURCES';
70+
sourceUuids: Array<string>;
71+
};
6772

6873
export type MessageFromBrowser<R extends ResponseFromBrowser> =
6974
| OutOfBandErrorMessageFromBrowser
@@ -128,6 +133,10 @@ type StatusQueryResponse = {
128133
// Shipped in Firefox 136.
129134
// Adds support for showing the JS script in DevTools debugger.
130135
// - OPEN_SCRIPT_IN_DEBUGGER
136+
// Version 6:
137+
// Shipped in Firefox 145.
138+
// Adds support for fetching JS sources.
139+
// - GET_JS_SOURCES
131140
version?: number;
132141
};
133142
type EnableMenuButtonResponse = void;
@@ -138,6 +147,8 @@ type GetSymbolTableResponse = SymbolTableAsTuple;
138147
type QuerySymbolicationApiResponse = string;
139148
type GetPageFaviconsResponse = Array<FaviconData | null>;
140149
type OpenScriptInTabDebuggerResponse = void;
150+
type GetJSSourceReponseItem = { sourceText: string } | { error: string };
151+
type GetJSSourcesResponse = Array<GetJSSourceReponseItem>;
141152

142153
// TypeScript function overloads for request/response pairs.
143154
function _sendMessageWithResponse(
@@ -167,6 +178,10 @@ function _sendMessageWithResponse(
167178
function _sendMessageWithResponse(
168179
request: OpenScriptInTabDebuggerRequest
169180
): Promise<OpenScriptInTabDebuggerResponse>;
181+
function _sendMessageWithResponse(
182+
request: GetJSSourcesRequest
183+
): Promise<GetJSSourcesResponse>;
184+
170185
function _sendMessageWithResponse(request: Request): Promise<any> {
171186
const requestId = _requestId++;
172187
const type = request.type;
@@ -371,6 +386,15 @@ export async function showFunctionInDevtoolsViaWebChannel(
371386
});
372387
}
373388

389+
export async function getJSSourcesViaWebChannel(
390+
sourceUuids: Array<string>
391+
): Promise<Array<GetJSSourceReponseItem>> {
392+
return _sendMessageWithResponse({
393+
type: 'GET_JS_SOURCES',
394+
sourceUuids,
395+
});
396+
}
397+
374398
/**
375399
* -----------------------------------------------------------------------------
376400
*

src/components/app/BottomBox.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { IonGraphView } from '../shared/IonGraphView';
1313
import { CodeLoadingOverlay } from './CodeLoadingOverlay';
1414
import { CodeErrorOverlay } from './CodeErrorOverlay';
1515
import {
16-
getSourceViewFile,
1716
getSourceViewScrollGeneration,
1817
getAssemblyViewIsOpen,
1918
getAssemblyViewNativeSymbol,
@@ -29,7 +28,10 @@ import {
2928
getSourceViewCode,
3029
getAssemblyViewCode,
3130
} from 'firefox-profiler/selectors/code';
32-
import { getPreviewSelectionIsBeingModified } from 'firefox-profiler/selectors/profile';
31+
import {
32+
getPreviewSelectionIsBeingModified,
33+
getSourceViewFile,
34+
} from 'firefox-profiler/selectors/profile';
3335
import explicitConnect from 'firefox-profiler/utils/connect';
3436

3537
import type { ConnectedProps } from 'firefox-profiler/utils/connect';

src/components/app/CodeErrorOverlay.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ export function CodeErrorOverlay({ errors }: CodeErrorOverlayProps) {
112112
</Localized>
113113
);
114114
}
115+
case 'NOT_PRESENT_IN_BROWSER': {
116+
const { sourceUuid, url, errorMessage } = error;
117+
return (
118+
<Localized
119+
id="SourceView--not-in-browser-error-when-obtaining-js-source"
120+
vars={{ url, sourceUuid, errorMessage }}
121+
>
122+
<li>{`The browser was unable to obtain the source file for ${url} with sourceUuid ${sourceUuid}: ${errorMessage}`}</li>
123+
</Localized>
124+
);
125+
}
115126
default:
116127
throw assertExhaustiveCheck(error);
117128
}

0 commit comments

Comments
 (0)