From d6c713a0fe6084e02bfd8d89ad93e2799b0605b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:13:25 -0400 Subject: [PATCH 01/16] Add WebChannel v7 support: Rename sourceUuids to sourceIds in GET_JS_SOURCES Version 7 of the WebChannel renames the GET_JS_SOURCES request field from `sourceUuids` to `sourceIds`. The sender picks the field name based on the negotiated WebChannel version so both older Firefox (v6) and newer (v7+) builds are supported. --- src/app-logic/browser-connection.ts | 11 +++++++++-- src/app-logic/web-channel.ts | 22 +++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/app-logic/browser-connection.ts b/src/app-logic/browser-connection.ts index 8a40db9c14..352eb6e181 100644 --- a/src/app-logic/browser-connection.ts +++ b/src/app-logic/browser-connection.ts @@ -12,7 +12,8 @@ import { querySymbolicationApiViaWebChannel, getPageFaviconsViaWebChannel, showFunctionInDevtoolsViaWebChannel, - getJSSourcesViaWebChannel, + getJSSourcesViaWebChannelV6, + getJSSourcesViaWebChannelV7, } from './web-channel'; import type { Milliseconds, @@ -96,6 +97,7 @@ export interface BrowserConnection { * the profile or symbols. So this class also supports the frame script. */ class BrowserConnectionImpl implements BrowserConnection { + _webChannelVersion: number; _webChannelSupportsGetProfileAndSymbolication: boolean; _webChannelSupportsGetExternalPowerTracks: boolean; _webChannelSupportsGetExternalMarkers: boolean; @@ -105,6 +107,7 @@ class BrowserConnectionImpl implements BrowserConnection { _geckoProfiler: $GeckoProfiler | undefined; constructor(webChannelVersion: number) { + this._webChannelVersion = webChannelVersion; this._webChannelSupportsGetProfileAndSymbolication = webChannelVersion >= 1; this._webChannelSupportsGetExternalPowerTracks = webChannelVersion >= 2; this._webChannelSupportsGetExternalMarkers = webChannelVersion >= 3; @@ -247,7 +250,11 @@ class BrowserConnectionImpl implements BrowserConnection { // fetching multiple sources, we only fetch one at a time currently. // TODO: Change this to fetch multiple JS sources at the load time or while // we share the profile. - return getJSSourcesViaWebChannel([sourceUuid]).then((sources) => { + const sourcesPromise = + this._webChannelVersion >= 7 + ? getJSSourcesViaWebChannelV7([sourceUuid]) + : getJSSourcesViaWebChannelV6([sourceUuid]); + return sourcesPromise.then((sources) => { const source = sources[0]; if ('error' in source) { throw new Error(source.error); diff --git a/src/app-logic/web-channel.ts b/src/app-logic/web-channel.ts index 4db2cf6825..3d8e125956 100644 --- a/src/app-logic/web-channel.ts +++ b/src/app-logic/web-channel.ts @@ -65,10 +65,11 @@ type OpenScriptInTabDebuggerRequest = { line: number | null; column: number | null; }; -type GetJSSourcesRequest = { - type: 'GET_JS_SOURCES'; - sourceUuids: Array; -}; +type GetJSSourcesRequest = + // Version 7+ uses sourceIds + | { type: 'GET_JS_SOURCES'; sourceIds: Array } + // Version 6 uses sourceUuids + | { type: 'GET_JS_SOURCES'; sourceUuids: Array }; export type MessageFromBrowser = | OutOfBandErrorMessageFromBrowser @@ -138,6 +139,8 @@ type StatusQueryResponse = { // Shipped in Firefox 145. // Adds support for fetching JS sources. // - GET_JS_SOURCES + // Version 7: + // Renames the GET_JS_SOURCES request field from `sourceUuids` to `sourceIds`. version?: number; }; type EnableMenuButtonResponse = void; @@ -387,7 +390,7 @@ export async function showFunctionInDevtoolsViaWebChannel( }); } -export async function getJSSourcesViaWebChannel( +export async function getJSSourcesViaWebChannelV6( sourceUuids: Array ): Promise> { return _sendMessageWithResponse({ @@ -396,6 +399,15 @@ export async function getJSSourcesViaWebChannel( }); } +export async function getJSSourcesViaWebChannelV7( + sourceIds: Array +): Promise> { + return _sendMessageWithResponse({ + type: 'GET_JS_SOURCES', + sourceIds, + }); +} + /** * ----------------------------------------------------------------------------- * From 36b2c72c3dfb45c6c0f03f64242ee8562202480c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:16:17 -0400 Subject: [PATCH 02/16] Add WebChannel v7 support: GET_SOURCE_MAP for source-map fetching Lets the browser fetch source maps from URLs the frontend cannot reach. The frontend identifies the bundle source via its sourceId, and the browser returns the parsed source map. Currently it's plumbing only. No caller dispatches GET_SOURCE_MAP yet. Adds source-map and url as direct dependencies; `source-map` provides the RawSourceMap type and runtime parser, and `url` is required by source-map's URL resolution in browser bundles. --- package.json | 2 ++ src/app-logic/browser-connection.ts | 21 ++++++++++++ src/app-logic/web-channel.ts | 26 +++++++++++++-- src/profile-query/function-annotate.ts | 5 +++ src/test/fixtures/mocks/web-channel.ts | 20 +++++++++++- src/test/unit/fetch-assembly.test.ts | 15 +++++++++ src/test/unit/fetch-source.test.ts | 45 ++++++++++++++++++++++++++ src/test/unit/query-api.test.ts | 8 +++++ src/utils/query-api.ts | 14 ++++++++ yarn.lock | 20 +++++++++++- 10 files changed, 172 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3f3aab20bb..f91ef0dbee 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,8 @@ "redux-logger": "^3.0.6", "redux-thunk": "^3.1.0", "reselect": "^4.1.8", + "source-map": "^0.7.6", + "url": "^0.11.4", "valibot": "^1.4.0", "workbox-window": "^7.4.1" }, diff --git a/src/app-logic/browser-connection.ts b/src/app-logic/browser-connection.ts index 352eb6e181..03ce6266d4 100644 --- a/src/app-logic/browser-connection.ts +++ b/src/app-logic/browser-connection.ts @@ -14,6 +14,7 @@ import { showFunctionInDevtoolsViaWebChannel, getJSSourcesViaWebChannelV6, getJSSourcesViaWebChannelV7, + getSourceMapViaWebChannel, } from './web-channel'; import type { Milliseconds, @@ -21,6 +22,7 @@ import type { MixedObject, SymbolTableAsTuple, } from 'firefox-profiler/types'; +import type { RawSourceMap } from 'source-map'; /** * This file manages the communication between the profiler and the browser. @@ -87,6 +89,14 @@ export interface BrowserConnection { ): Promise; getJSSource(sourceUuid: string): Promise; + + // Get source map of the given source directly from the browser. + // Requires WebChannel version 7+. + getSourceMap(sourceId: string): Promise; + + // True when the browser exposes GET_SOURCE_MAP (WebChannel version 7+). + // Callers use this to gate source-map-based features. + readonly supportsGetSourceMap: boolean; } /** @@ -104,6 +114,7 @@ class BrowserConnectionImpl implements BrowserConnection { _webChannelSupportsGetPageFavicons: boolean; _webChannelSupportsOpenDebuggerInTab: boolean; _webChannelSupportsGetJSSource: boolean; + readonly supportsGetSourceMap: boolean; _geckoProfiler: $GeckoProfiler | undefined; constructor(webChannelVersion: number) { @@ -114,6 +125,7 @@ class BrowserConnectionImpl implements BrowserConnection { this._webChannelSupportsGetPageFavicons = webChannelVersion >= 4; this._webChannelSupportsOpenDebuggerInTab = webChannelVersion >= 5; this._webChannelSupportsGetJSSource = webChannelVersion >= 6; + this.supportsGetSourceMap = webChannelVersion >= 7; } // Only called when we must obtain the profile from the browser, i.e. if we @@ -235,6 +247,15 @@ class BrowserConnectionImpl implements BrowserConnection { return []; } + async getSourceMap(sourceId: string): Promise { + if (!this.supportsGetSourceMap) { + throw new Error( + "Can't use getSourceMap in Firefox versions with the old WebChannel." + ); + } + return getSourceMapViaWebChannel(sourceId); + } + /** * Fetches JavaScript source code from the browser using the source UUID. * This method requires WebChannel version 6 or higher (Firefox 145+). diff --git a/src/app-logic/web-channel.ts b/src/app-logic/web-channel.ts index 3d8e125956..3316c6e822 100644 --- a/src/app-logic/web-channel.ts +++ b/src/app-logic/web-channel.ts @@ -8,6 +8,7 @@ import type { ExternalMarkersData, FaviconData, } from 'firefox-profiler/types'; +import type { RawSourceMap } from 'source-map'; /** * This file is in charge of handling the message managing between profiler.firefox.com @@ -29,7 +30,8 @@ export type Request = | QuerySymbolicationApiRequest | GetPageFaviconsRequest | OpenScriptInTabDebuggerRequest - | GetJSSourcesRequest; + | GetJSSourcesRequest + | GetSourceMapRequest; type StatusQueryRequest = { type: 'STATUS_QUERY' }; type EnableMenuButtonRequest = { type: 'ENABLE_MENU_BUTTON' }; @@ -70,6 +72,10 @@ type GetJSSourcesRequest = | { type: 'GET_JS_SOURCES'; sourceIds: Array } // Version 6 uses sourceUuids | { type: 'GET_JS_SOURCES'; sourceUuids: Array }; +type GetSourceMapRequest = { + type: 'GET_SOURCE_MAP'; + sourceId: string; +}; export type MessageFromBrowser = | OutOfBandErrorMessageFromBrowser @@ -140,7 +146,10 @@ type StatusQueryResponse = { // Adds support for fetching JS sources. // - GET_JS_SOURCES // Version 7: - // Renames the GET_JS_SOURCES request field from `sourceUuids` to `sourceIds`. + // Adds support for fetching source maps via the browser, which can access + // URLs that the frontend cannot. + // - GET_SOURCE_MAP + // Also renames the GET_JS_SOURCES request field from `sourceUuids` to `sourceIds`. version?: number; }; type EnableMenuButtonResponse = void; @@ -153,6 +162,7 @@ type GetPageFaviconsResponse = Array; type OpenScriptInTabDebuggerResponse = void; type GetJSSourceReponseItem = { sourceText: string } | { error: string }; type GetJSSourcesResponse = Array; +type GetSourceMapResponse = RawSourceMap; // TypeScript function overloads for request/response pairs. function _sendMessageWithResponse( @@ -185,6 +195,9 @@ function _sendMessageWithResponse( function _sendMessageWithResponse( request: GetJSSourcesRequest ): Promise; +function _sendMessageWithResponse( + request: GetSourceMapRequest +): Promise; function _sendMessageWithResponse(request: Request): Promise { const requestId = _requestId++; @@ -408,6 +421,15 @@ export async function getJSSourcesViaWebChannelV7( }); } +export async function getSourceMapViaWebChannel( + sourceId: string +): Promise { + return _sendMessageWithResponse({ + type: 'GET_SOURCE_MAP', + sourceId, + }); +} + /** * ----------------------------------------------------------------------------- * diff --git a/src/profile-query/function-annotate.ts b/src/profile-query/function-annotate.ts index 4a94d96f26..ddb4ee0e90 100644 --- a/src/profile-query/function-annotate.ts +++ b/src/profile-query/function-annotate.ts @@ -24,6 +24,7 @@ import { import { fetchAssembly } from 'firefox-profiler/utils/fetch-assembly'; import { fetchSource } from 'firefox-profiler/utils/fetch-source'; import type { ExternalCommunicationDelegate } from 'firefox-profiler/utils/query-api'; +import type { RawSourceMap } from 'source-map'; import type { Profile, IndexIntoFuncTable, @@ -56,6 +57,10 @@ class NodeExternalCommunicationDelegate implements ExternalCommunicationDelegate async fetchJSSourceFromBrowser(_source: string): Promise { throw new Error('No browser connection available in profiler-cli'); } + + async fetchSourceMapFromBrowser(_sourceId: string): Promise { + throw new Error('No browser connection available in profiler-cli'); + } } const nodeDelegate = new NodeExternalCommunicationDelegate(); diff --git a/src/test/fixtures/mocks/web-channel.ts b/src/test/fixtures/mocks/web-channel.ts index d9a8b326b6..c2bb2ca72d 100644 --- a/src/test/fixtures/mocks/web-channel.ts +++ b/src/test/fixtures/mocks/web-channel.ts @@ -143,7 +143,7 @@ export function simulateWebChannel( requestId: message.requestId, response: { menuButtonIsEnabled: true, - version: 5, + version: 7, }, }); break; @@ -210,6 +210,24 @@ export function simulateWebChannel( }); break; } + case 'GET_JS_SOURCES': { + triggerResponse({ + type: 'ERROR_RESPONSE', + requestId: message.requestId, + error: + 'GET_JS_SOURCES is a valid message but not covered by this test.', + }); + break; + } + case 'GET_SOURCE_MAP': { + triggerResponse({ + type: 'ERROR_RESPONSE', + requestId: message.requestId, + error: + 'GET_SOURCE_MAP is a valid message but not covered by this test.', + }); + break; + } default: { break; diff --git a/src/test/unit/fetch-assembly.test.ts b/src/test/unit/fetch-assembly.test.ts index a03404d8b0..890d4fba31 100644 --- a/src/test/unit/fetch-assembly.test.ts +++ b/src/test/unit/fetch-assembly.test.ts @@ -77,6 +77,9 @@ describe('fetchAssembly', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, }) ).toEqual({ type: 'SUCCESS', @@ -151,6 +154,9 @@ describe('fetchAssembly', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).type @@ -184,6 +190,9 @@ describe('fetchAssembly', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).type @@ -205,6 +214,9 @@ describe('fetchAssembly', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, }) ).toEqual({ type: 'ERROR', @@ -246,6 +258,9 @@ describe('fetchAssembly', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).type diff --git a/src/test/unit/fetch-source.test.ts b/src/test/unit/fetch-source.test.ts index 7bb7b38e21..027e8db6ee 100644 --- a/src/test/unit/fetch-source.test.ts +++ b/src/test/unit/fetch-source.test.ts @@ -31,6 +31,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -75,6 +78,9 @@ describe('fetchSource', function () { const fetchJSSourceFromBrowser = async (_sourceUuid: string) => { throw new Error('No browser connection'); }; + const fetchSourceMapFromBrowser = async (_sourceId: string) => { + throw new Error('No browser connection'); + }; const archiveCache = new Map>(); @@ -89,6 +95,7 @@ describe('fetchSource', function () { fetchUrlResponse, queryBrowserSymbolicationApi, fetchJSSourceFromBrowser, + fetchSourceMapFromBrowser, } ) ).toEqual({ @@ -112,6 +119,7 @@ describe('fetchSource', function () { fetchUrlResponse, queryBrowserSymbolicationApi, fetchJSSourceFromBrowser, + fetchSourceMapFromBrowser, } ) ).toEqual({ @@ -133,6 +141,7 @@ describe('fetchSource', function () { fetchUrlResponse, queryBrowserSymbolicationApi, fetchJSSourceFromBrowser, + fetchSourceMapFromBrowser, } ) ).toEqual({ @@ -171,6 +180,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -218,6 +230,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -272,6 +287,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -326,6 +344,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -378,6 +399,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -412,6 +436,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -456,6 +483,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -492,6 +522,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -541,6 +574,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('No browser connection'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -581,6 +617,9 @@ describe('fetchSource', function () { } throw new Error(`Unexpected source: ${sourceUuid}`); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -612,6 +651,9 @@ describe('fetchSource', function () { `Source not found for source with ID: ${sourceUuid}` ); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ @@ -655,6 +697,9 @@ describe('fetchSource', function () { fetchJSSourceFromBrowser: async (_sourceUuid: string) => { throw new Error('Source not found in browser'); }, + fetchSourceMapFromBrowser: async (_sourceId: string) => { + throw new Error('No browser connection'); + }, } ) ).toEqual({ diff --git a/src/test/unit/query-api.test.ts b/src/test/unit/query-api.test.ts index f760d7bc35..a96215c915 100644 --- a/src/test/unit/query-api.test.ts +++ b/src/test/unit/query-api.test.ts @@ -35,6 +35,12 @@ describe('queryApiWithFallback', function () { throw new Error('Not implemented'); }) ), + fetchSourceMapFromBrowser: jest.fn( + overrides.fetchSourceMapFromBrowser ?? + (async () => { + throw new Error('Not implemented'); + }) + ), }; } @@ -278,6 +284,8 @@ describe('RegularExternalCommunicationDelegate', function () { getPageFavicons: jest.fn(bcOverrides.getPageFavicons), showFunctionInDevtools: jest.fn(bcOverrides.showFunctionInDevtools), getJSSource: jest.fn(bcOverrides.getJSSource), + getSourceMap: jest.fn(bcOverrides.getSourceMap), + supportsGetSourceMap: false, } : null; diff --git a/src/utils/query-api.ts b/src/utils/query-api.ts index a72764dfe1..975e2d4098 100644 --- a/src/utils/query-api.ts +++ b/src/utils/query-api.ts @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import type { RawSourceMap } from 'source-map'; import type { MixedObject, ApiQueryError } from 'firefox-profiler/types'; import type { BrowserConnection } from 'firefox-profiler/app-logic/browser-connection'; @@ -25,6 +26,10 @@ export interface ExternalCommunicationDelegate { ): Promise; fetchJSSourceFromBrowser(source: string): Promise; + + // Get source map of the given source directly from the browser. + // Requires WebChannel version 7+. + fetchSourceMapFromBrowser(sourceId: string): Promise; } export type ApiQueryResult = @@ -169,4 +174,13 @@ export class RegularExternalCommunicationDelegate implements ExternalCommunicati this._callbacks.onBeginBrowserConnectionQuery(); return browserConnection.getJSSource(source); } + + fetchSourceMapFromBrowser(sourceId: string): Promise { + const browserConnection = this._browserConnection; + if (browserConnection === null) { + throw new Error('No connection to the browser.'); + } + this._callbacks.onBeginBrowserConnectionQuery(); + return browserConnection.getSourceMap(sourceId); + } } diff --git a/yarn.lock b/yarn.lock index 843c7cf975..aad78a5867 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9224,6 +9224,11 @@ pump@^1.0.1: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -9253,7 +9258,7 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@^6.5.2: +qs@^6.12.3, qs@^6.5.2: version "6.15.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9" integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw== @@ -10233,6 +10238,11 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + source-map@^0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" @@ -11399,6 +11409,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url@^0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c" + integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg== + dependencies: + punycode "^1.4.1" + qs "^6.12.3" + use-sync-external-store@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" From bc4809d7661e445a681eb30626b05973ebd507d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:22:44 -0400 Subject: [PATCH 03/16] Add SourceLocationTable and sources.content to profile format Extends the processed profile with a per-thread-shared SourceLocationTable (stored at profile.shared.originalLocation) and a content column on the SourceTable. Each frame and func now has a nullable originalLocation index, set to null by all existing initializers and upgraders. Source content stays null until JS symbolication populates it. The new tables are plumbed through data-structures, profile-compacting, merge-compare, and every importer. It adds a v63 upgrader and a CHANGELOG entry. No call site reads the new columns yet, behavior is unchanged. --- docs-developer/CHANGELOG-formats.md | 15 + src/app-logic/constants.ts | 2 +- src/profile-logic/data-structures.ts | 27 + src/profile-logic/global-data-collector.ts | 4 + src/profile-logic/import/chrome.ts | 1 + src/profile-logic/import/dhat.ts | 2 + src/profile-logic/import/flame-graph.ts | 1 + src/profile-logic/import/simpleperf.ts | 4 + src/profile-logic/js-tracer.ts | 2 + src/profile-logic/merge-compare.ts | 84 + src/profile-logic/process-profile.ts | 1 + .../processed-profile-versioning.ts | 15 + src/profile-logic/profile-compacting.ts | 21 + src/profile-logic/profile-data.ts | 6 +- src/profile-logic/symbolication.ts | 3 + src/selectors/per-thread/thread.tsx | 2 + src/test/fixtures/profiles/call-nodes.ts | 2 + .../fixtures/profiles/processed-profile.ts | 4 + src/test/fixtures/utils.ts | 4 +- .../__snapshots__/profiler-edit.test.ts.snap | 272 +- .../__snapshots__/profile-view.test.ts.snap | 176 +- .../profile-conversion.test.ts.snap | 41507 +++++++++++++--- .../profile-upgrading.test.ts.snap | 424 +- src/test/unit/merge-compare.test.ts | 117 + src/types/profile-derived.ts | 3 + src/types/profile.ts | 36 + 26 files changed, 35912 insertions(+), 6823 deletions(-) diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index 50da5c96d4..f715f96ab2 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -6,6 +6,21 @@ Note that this is not an exhaustive list. Processed profile format upgraders can ## Processed profile format +### Version 63 + +A new `SourceLocationTable` has been added to `profile.shared.originalLocation`. It holds the original (pre-compilation) source positions produced by source map symbolication, paired with the generated `line`/`column` already on `FrameTable`. + +- `source: IndexIntoSourceTable[]`: source file index. Set independently for func entries (the function's definition file) and frame entries (the execution point's file). +- `line: number[]`: 1-based line number +- `column: number[]`: 1-based column number + +Two new columns were added that index into this table: + +- `FrameTable.originalLocation: Array`: the original execution point for the frame +- `FuncTable.originalLocation: Array`: the original definition site for the function + +A new `content: Array` column was added to `SourceTable`. + ### Version 62 A new `display` field of type `CounterDisplayConfig` was added to `RawCounter`. diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index a8bdb0e23c..ef712c02b8 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 34; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const PROCESSED_PROFILE_VERSION = 62; +export const PROCESSED_PROFILE_VERSION = 63; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 0f25fe3e39..8d8307f1b5 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -26,6 +26,7 @@ import type { JsTracerTable, CallNodeTable, SourceTable, + SourceLocationTable, } from 'firefox-profiler/types'; /** @@ -93,6 +94,7 @@ export function getEmptyFrameTable(): FrameTable { innerWindowID: [], line: [], column: [], + originalLocation: [], length: 0, }; } @@ -112,6 +114,7 @@ export function shallowCloneFrameTable(frameTable: FrameTable): FrameTable { innerWindowID: frameTable.innerWindowID.slice(), line: frameTable.line.slice(), column: frameTable.column.slice(), + originalLocation: frameTable.originalLocation.slice(), length: frameTable.length, }; } @@ -129,6 +132,7 @@ export function getEmptyFuncTable(): FuncTable { source: [], lineNumber: [], columnNumber: [], + originalLocation: [], length: 0, }; } @@ -146,10 +150,31 @@ export function shallowCloneFuncTable(funcTable: FuncTable): FuncTable { source: funcTable.source.slice(), lineNumber: funcTable.lineNumber.slice(), columnNumber: funcTable.columnNumber.slice(), + originalLocation: funcTable.originalLocation.slice(), length: funcTable.length, }; } +export function getEmptySourceLocationTable(): SourceLocationTable { + return { + source: [], + line: [], + column: [], + length: 0, + }; +} + +export function shallowCloneSourceLocationTable( + originalLocation: SourceLocationTable +): SourceLocationTable { + return { + source: originalLocation.source.slice(), + line: originalLocation.line.slice(), + column: originalLocation.column.slice(), + length: originalLocation.length, + }; +} + export function shallowCloneNativeSymbolTable( nativeSymbols: NativeSymbolTable ): NativeSymbolTable { @@ -338,6 +363,7 @@ export function getEmptySourceTable(): SourceTable { startLine: [], startColumn: [], sourceMapURL: [], + content: [], length: 0, }; } @@ -374,6 +400,7 @@ export function getEmptySharedData(): RawProfileSharedData { nativeSymbols: getEmptyNativeSymbolTable(), sources: getEmptySourceTable(), stringArray: [], + originalLocation: getEmptySourceLocationTable(), }; } diff --git a/src/profile-logic/global-data-collector.ts b/src/profile-logic/global-data-collector.ts index a928534898..ec0957a162 100644 --- a/src/profile-logic/global-data-collector.ts +++ b/src/profile-logic/global-data-collector.ts @@ -10,6 +10,7 @@ import { getEmptyRawStackTable, getEmptyResourceTable, getEmptySourceTable, + getEmptySourceLocationTable, } from './data-structures'; import type { @@ -108,6 +109,7 @@ export class GlobalDataCollector { this._funcTable.source[funcIndex] = source; this._funcTable.lineNumber[funcIndex] = lineNumber; this._funcTable.columnNumber[funcIndex] = columnNumber; + this._funcTable.originalLocation[funcIndex] = null; this._funcKeyToFuncIndex.set(funcKey, funcIndex); } return funcIndex; @@ -144,6 +146,7 @@ export class GlobalDataCollector { this._sources.startLine[index] = startLine; this._sources.startColumn[index] = startColumn; this._sources.sourceMapURL[index] = sourceMapURLIndex; + this._sources.content[index] = null; this._sources.length++; if (id !== null) { @@ -314,6 +317,7 @@ export class GlobalDataCollector { nativeSymbols: this._nativeSymbols, stringArray: this._stringArray, sources: this._sources, + originalLocation: getEmptySourceLocationTable(), }; return { libs: this._libs, shared }; diff --git a/src/profile-logic/import/chrome.ts b/src/profile-logic/import/chrome.ts index b49c570ffc..96add0a137 100644 --- a/src/profile-logic/import/chrome.ts +++ b/src/profile-logic/import/chrome.ts @@ -646,6 +646,7 @@ async function processTracingEvents( lineNumber === undefined ? null : lineNumber; frameTable.column[frameIndex] = columnNumber === undefined ? null : columnNumber; + frameTable.originalLocation[frameIndex] = null; frameTable.length = Math.max(frameTable.length, frameIndex + 1); stackTable.frame.push(frameIndex); diff --git a/src/profile-logic/import/dhat.ts b/src/profile-logic/import/dhat.ts index f5f58d4fed..dc56c06f17 100644 --- a/src/profile-logic/import/dhat.ts +++ b/src/profile-logic/import/dhat.ts @@ -214,6 +214,7 @@ export function attemptToConvertDhat(json: unknown): Profile | null { frameTable.nativeSymbol.push(null); frameTable.inlineDepth.push(0); frameTable.func.push(rootFuncIndex); + frameTable.originalLocation.push(null); const rootFrameIndex = frameTable.length++; stackTable.frame.push(rootFrameIndex); @@ -280,6 +281,7 @@ export function attemptToConvertDhat(json: unknown): Profile | null { frameTable.nativeSymbol.push(null); frameTable.inlineDepth.push(0); frameTable.func.push(funcIndex); + frameTable.originalLocation.push(null); frameTable.length++; } diff --git a/src/profile-logic/import/flame-graph.ts b/src/profile-logic/import/flame-graph.ts index c941f2137b..148a777bd2 100644 --- a/src/profile-logic/import/flame-graph.ts +++ b/src/profile-logic/import/flame-graph.ts @@ -122,6 +122,7 @@ export function convertFlameGraphProfile(profileText: string): Profile { frameTable.innerWindowID.push(null); frameTable.line.push(null); frameTable.column.push(null); + frameTable.originalLocation.push(null); frameTable.length++; frameMap.set(frameString, frameIndex); diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index 152830e49c..ce933b9a7c 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -29,6 +29,7 @@ import { getEmptyRawMarkerTable, getEmptyNativeSymbolTable, getEmptySourceTable, + getEmptySourceLocationTable, } from 'firefox-profiler/profile-logic/data-structures'; import { StringTable } from 'firefox-profiler/utils/string-table'; import { ensureExists } from 'firefox-profiler/utils/types'; @@ -134,6 +135,7 @@ class FirefoxFuncTable { this.funcTable.source.push(null); this.funcTable.lineNumber.push(null); this.funcTable.columnNumber.push(null); + this.funcTable.originalLocation.push(null); funcIndex = this.funcTable.length++; this.funcMap.set(mapKey, funcIndex); @@ -174,6 +176,7 @@ class FirefoxFrameTable { this.frameTable.innerWindowID.push(null); this.frameTable.line.push(null); this.frameTable.column.push(null); + this.frameTable.originalLocation.push(null); frameIndex = this.frameTable.length++; this.frameMap.set(mapKey, frameIndex); @@ -233,6 +236,7 @@ class FirefoxSharedData { nativeSymbols: getEmptyNativeSymbolTable(), sources: getEmptySourceTable(), stringArray: this.stringArray, + originalLocation: getEmptySourceLocationTable(), }; } } diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index 17af033347..8993ddfbca 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -571,6 +571,7 @@ export function convertJsTracerToThreadWithoutSamples( funcTable.source.push(null); funcTable.lineNumber.push(null); funcTable.columnNumber.push(null); + funcTable.originalLocation.push(null); funcMap.set(eventStringIndex, funcIndex); } else { @@ -601,6 +602,7 @@ export function convertJsTracerToThreadWithoutSamples( frameTable.innerWindowID.push(0); frameTable.line.push(line); frameTable.column.push(column); + frameTable.originalLocation.push(null); // Each event gets a stack table entry. const stackIndex = stackTable.length++; diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index 269fc4fc46..e8a6e9558d 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -48,6 +48,7 @@ import type { IndexIntoStackTable, IndexIntoStringTable, IndexIntoSourceTable, + IndexIntoSourceLocationTable, FuncTable, FrameTable, Lib, @@ -56,6 +57,7 @@ import type { RawSamplesTable, RawStackTable, SourceTable, + SourceLocationTable, UrlState, ImplementationFilter, TransformStacksPerThread, @@ -374,6 +376,7 @@ type TranslationMapForStacks = Int32Array; type TranslationMapForLibs = Int32Array; type TranslationMapForStrings = Int32Array; type TranslationMapForSources = Int32Array; +type TranslationMapForOriginalLocation = Int32Array; /** * Merges several categories lists into one, resolving duplicates if necessary. @@ -442,6 +445,10 @@ export function mergeSharedData(profiles: Profile[]): { profiles.map((profile) => profile.shared.sources ?? null), translationMapsForStrings ); + const { + originalLocation: newOriginalLocation, + translationMaps: translationMapsForOriginalLocation, + } = mergeSourceLocationTables(profiles, translationMapsForSources); const { resourceTable: newResourceTable, translationMaps: translationMapsForResources, @@ -463,6 +470,7 @@ export function mergeSharedData(profiles: Profile[]): { profiles, translationMapsForResources, translationMapsForSources, + translationMapsForOriginalLocation, translationMapsForStrings ); const { @@ -472,6 +480,7 @@ export function mergeSharedData(profiles: Profile[]): { profiles, translationMapsForFuncs, translationMapsForNativeSymbols, + translationMapsForOriginalLocation, translationMapsForCategories ); const { @@ -487,6 +496,7 @@ export function mergeSharedData(profiles: Profile[]): { resourceTable: newResourceTable, stringArray: newStringArray, sources: newSources, + originalLocation: newOriginalLocation, }; const translationMapsPerProfile = profiles.map((profile, i) => { @@ -581,6 +591,7 @@ function mergeSources( newSources.startLine[insertedSourceIndex] = sources.startLine[i]; newSources.startColumn[insertedSourceIndex] = sources.startColumn[i]; newSources.sourceMapURL[insertedSourceIndex] = newSourceMapURLIndex; + newSources.content[insertedSourceIndex] = sources.content[i]; newSources.length++; mapOfInsertedSources.set(sourceKey, insertedSourceIndex); } @@ -597,6 +608,52 @@ function mergeSources( }; } +/** + * Combines the originalLocation tables from multiple profiles. Each row is copied + * verbatim except `source`, which is translated through the source + * translation map. Rows are not deduplicated across profiles. This is leaf + * data with no downstream constraint that would benefit from a stable key. + */ +function mergeSourceLocationTables( + profiles: ReadonlyArray, + translationMapsForSources: TranslationMapForSources[] +): { + originalLocation: SourceLocationTable; + translationMaps: TranslationMapForOriginalLocation[]; +} { + const newOriginalLocation: SourceLocationTable = { + source: [], + line: [], + column: [], + length: 0, + }; + + const translationMaps = profiles.map((profile, profileIndex) => { + const originalLocation = profile.shared.originalLocation; + const sourceTranslation = translationMapsForSources[profileIndex]; + const oldOriginalLocationToNewPlusOne = new Int32Array( + originalLocation.length + ); + + for (let i = 0; i < originalLocation.length; i++) { + const source = originalLocation.source[i]; + const newOriginalSource = sourceTranslation[source] - 1; + + const newIndex = newOriginalLocation.length; + newOriginalLocation.source.push(newOriginalSource); + newOriginalLocation.line.push(originalLocation.line[i]); + newOriginalLocation.column.push(originalLocation.column[i]); + newOriginalLocation.length++; + + oldOriginalLocationToNewPlusOne[i] = newIndex + 1; + } + + return oldOriginalLocationToNewPlusOne; + }); + + return { originalLocation: newOriginalLocation, translationMaps }; +} + function adjustStringIndexes( stringIndexes: ReadonlyArray, oldStringToNewStringPlusOne: TranslationMapForStrings @@ -713,6 +770,15 @@ function _mapNullableSource( : null; } +function _mapNullableOriginalLocation( + originalLocationIndex: IndexIntoSourceLocationTable | null, + oldOriginalLocationToNewPlusOne: TranslationMapForOriginalLocation +): IndexIntoSourceLocationTable | null { + return originalLocationIndex !== null + ? oldOriginalLocationToNewPlusOne[originalLocationIndex] - 1 + : null; +} + function _mapFuncResource( resourceIndex: IndexIntoResourceTable | -1, oldResourceToNewResourcePlusOne: TranslationMapForResources @@ -900,6 +966,7 @@ function mergeFuncTables( profiles: ReadonlyArray, translationMapsForResources: TranslationMapForResources[], translationMapsForSources: TranslationMapForSources[], + translationMapsForOriginalLocation: TranslationMapForOriginalLocation[], translationMapsForStrings: TranslationMapForStrings[] ): { funcTable: FuncTable; translationMaps: TranslationMapForFuncs[] } { const mapOfInsertedFuncs = new Map(); @@ -912,6 +979,8 @@ function mergeFuncTables( translationMapsForResources[profileIndex]; const oldStringToNewStringPlusOne = translationMapsForStrings[profileIndex]; const oldSourceToNewSourcePlusOne = translationMapsForSources[profileIndex]; + const oldOriginalLocationToNewPlusOne = + translationMapsForOriginalLocation[profileIndex]; const oldFuncToNewFuncPlusOne = new Int32Array(funcTable.length); for (let i = 0; i < funcTable.length; i++) { @@ -953,6 +1022,12 @@ function mergeFuncTables( newFuncTable.source.push(sourceIndex); newFuncTable.lineNumber.push(lineNumber); newFuncTable.columnNumber.push(funcTable.columnNumber[i]); + newFuncTable.originalLocation.push( + _mapNullableOriginalLocation( + funcTable.originalLocation[i], + oldOriginalLocationToNewPlusOne + ) + ); newFuncTable.length++; } @@ -974,6 +1049,7 @@ function mergeFrameTables( profiles: ReadonlyArray, translationMapsForFuncs: TranslationMapForFuncs[], translationMapsForNativeSymbols: TranslationMapForNativeSymbols[], + translationMapsForOriginalLocation: TranslationMapForOriginalLocation[], translationMapsForCategories: TranslationMapForCategories[] ): { frameTable: FrameTable; translationMaps: TranslationMapForFrames[] } { const translationMaps: TranslationMapForFrames[] = []; @@ -984,6 +1060,8 @@ function mergeFrameTables( const oldFuncToNewFuncPlusOne = translationMapsForFuncs[profileIndex]; const oldNativeSymbolToNewNativeSymbolPlusOne = translationMapsForNativeSymbols[profileIndex]; + const oldOriginalLocationToNewPlusOne = + translationMapsForOriginalLocation[profileIndex]; const oldCategoryToNewCategoryPlusOne = translationMapsForCategories[profileIndex]; const oldFrameToNewFramePlusOne = new Int32Array(frameTable.length); @@ -1010,6 +1088,12 @@ function mergeFrameTables( newFrameTable.innerWindowID.push(frameTable.innerWindowID[i]); newFrameTable.line.push(frameTable.line[i]); newFrameTable.column.push(frameTable.column[i]); + newFrameTable.originalLocation.push( + _mapNullableOriginalLocation( + frameTable.originalLocation[i], + oldOriginalLocationToNewPlusOne + ) + ); oldFrameToNewFramePlusOne[i] = newFrameTable.length + 1; newFrameTable.length++; diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index d1f44222dc..841566ff75 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -529,6 +529,7 @@ function _processFrameTable( geckoFrameStruct.innerWindowID[i]; sharedFrameTable.line[newIndex] = geckoFrameStruct.line[i]; sharedFrameTable.column[newIndex] = geckoFrameStruct.column[i]; + sharedFrameTable.originalLocation[newIndex] = null; } sharedFrameTable.length += geckoFrameStruct.length; return frameIndexOffset; diff --git a/src/profile-logic/processed-profile-versioning.ts b/src/profile-logic/processed-profile-versioning.ts index 725b597997..fa1ec41355 100644 --- a/src/profile-logic/processed-profile-versioning.ts +++ b/src/profile-logic/processed-profile-versioning.ts @@ -3110,6 +3110,21 @@ const _upgraders: { } }, + [63]: (profile: any) => { + // Add the originalLocation table on profile.shared and the matching + // originalLocation column on funcTable and frameTable. Also add the content + // column on the sources table. + const { funcTable, frameTable, sources } = profile.shared; + funcTable.originalLocation = new Array(funcTable.length).fill(null); + frameTable.originalLocation = new Array(frameTable.length).fill(null); + profile.shared.originalLocation = { + source: [], + line: [], + column: [], + length: 0, + }; + sources.content = new Array(sources.length).fill(null); + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. }; diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index e575383286..444bfc0f11 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -18,6 +18,7 @@ import type { NativeSymbolTable, Lib, SourceTable, + SourceLocationTable, } from 'firefox-profiler/types'; import { assertExhaustiveCheck, @@ -104,6 +105,7 @@ type TableCompactionStates = { resourceTable: TableCompactionState; nativeSymbols: TableCompactionState; sources: TableCompactionState; + originalLocation: TableCompactionState; stringArray: TableCompactionState; libs: TableCompactionState; }; @@ -135,6 +137,7 @@ export function computeCompactedProfile( resourceTable: new TableCompactionState(shared.resourceTable.length), nativeSymbols: new TableCompactionState(shared.nativeSymbols.length), sources: new TableCompactionState(shared.sources.length), + originalLocation: new TableCompactionState(shared.originalLocation.length), libs: new TableCompactionState(profile.libs.length), stringArray: new TableCompactionState(shared.stringArray.length), }; @@ -153,6 +156,7 @@ export function computeCompactedProfile( innerWindowID: ColDesc.noRef(), line: ColDesc.noRef(), column: ColDesc.noRef(), + originalLocation: ColDesc.indexRefOrNull(tcs.originalLocation), }; const funcTableDesc: TableDescription = { name: ColDesc.indexRef(tcs.stringArray), @@ -162,6 +166,12 @@ export function computeCompactedProfile( source: ColDesc.indexRefOrNull(tcs.sources), lineNumber: ColDesc.noRef(), columnNumber: ColDesc.noRef(), + originalLocation: ColDesc.indexRefOrNull(tcs.originalLocation), + }; + const originalLocationDesc: TableDescription = { + source: ColDesc.indexRef(tcs.sources), + line: ColDesc.noRef(), + column: ColDesc.noRef(), }; const resourceTableDesc: TableDescription = { name: ColDesc.indexRef(tcs.stringArray), @@ -181,6 +191,7 @@ export function computeCompactedProfile( startLine: ColDesc.noRef(), startColumn: ColDesc.noRef(), sourceMapURL: ColDesc.indexRefOrNull(tcs.stringArray), + content: ColDesc.noRef(), }; // Step 1: Gather all references. @@ -215,6 +226,11 @@ export function computeCompactedProfile( tcs.resourceTable, resourceTableDesc ); + _markTableAndComputeTranslation( + shared.originalLocation, + tcs.originalLocation, + originalLocationDesc + ); _markTableAndComputeTranslation( shared.nativeSymbols, tcs.nativeSymbols, @@ -250,6 +266,11 @@ export function computeCompactedProfile( nativeSymbolsDesc ), sources: _compactTable(shared.sources, tcs.sources, sourcesDesc), + originalLocation: _compactTable( + shared.originalLocation, + tcs.originalLocation, + originalLocationDesc + ), stringArray: _createCompactedStringArray(shared.stringArray, tcs), }; diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 89ed4bd8f6..31d1d6482c 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -101,6 +101,7 @@ import type { IndexIntoSourceTable, TransformOutput, SampleCategoriesAndSubcategories, + SourceLocationTable, } from 'firefox-profiler/types'; import { SelectedState, ResourceType } from 'firefox-profiler/types'; import type { CallNodeInfo, SuffixOrderIndex } from './call-node-info'; @@ -2784,7 +2785,8 @@ export function createThreadFromDerivedTables( resourceTable: ResourceTable, stringTable: StringTable, sources: SourceTable, - tracedValuesBuffer: ArrayBuffer | undefined + tracedValuesBuffer: ArrayBuffer | undefined, + originalLocation: SourceLocationTable ): Thread { const { processType, @@ -2844,6 +2846,7 @@ export function createThreadFromDerivedTables( stringTable, sources, tracedValuesBuffer, + originalLocation, }; return thread; } @@ -3440,6 +3443,7 @@ export function reserveFunctionsForCollapsedResources( funcTable.source.push(null); funcTable.lineNumber.push(null); funcTable.columnNumber.push(null); + funcTable.originalLocation.push(null); funcTable.length++; reservedFunctionsForResources.set(resourceIndex, funcIndex); } diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index cdc01ac0d2..a6370bc6fa 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -795,6 +795,7 @@ function _partiallyApplySymbolicationStep( funcTable.source[funcIndex] = null; funcTable.lineNumber[funcIndex] = null; funcTable.columnNumber[funcIndex] = null; + funcTable.originalLocation[funcIndex] = null; // The name field will be filled below. funcTable.length++; } @@ -819,6 +820,7 @@ function _partiallyApplySymbolicationStep( sources.startLine.push(1); sources.startColumn.push(1); sources.sourceMapURL.push(null); + sources.content.push(null); sources.length++; } funcTable.source[funcIndex] = sourceIndex; @@ -851,6 +853,7 @@ function _partiallyApplySymbolicationStep( frameTable.innerWindowID[expansionFrameIndex] = innerWindowID; frameTable.address[expansionFrameIndex] = address; frameTable.nativeSymbol[expansionFrameIndex] = nativeSymbolIndex; + frameTable.originalLocation[expansionFrameIndex] = null; // These remaining fields are filled below. } diff --git a/src/selectors/per-thread/thread.tsx b/src/selectors/per-thread/thread.tsx index 59db24d074..cd563ffcf0 100644 --- a/src/selectors/per-thread/thread.tsx +++ b/src/selectors/per-thread/thread.tsx @@ -193,6 +193,8 @@ export function getBasicThreadSelectorsPerThread( ProfileSelectors.getStringTable, ProfileSelectors.getSourceTable, getTracedValuesBuffer, + (state: State) => + ProfileSelectors.getRawProfileSharedData(state).originalLocation, ProfileData.createThreadFromDerivedTables ); diff --git a/src/test/fixtures/profiles/call-nodes.ts b/src/test/fixtures/profiles/call-nodes.ts index 7b68a84c87..909b48e7e7 100644 --- a/src/test/fixtures/profiles/call-nodes.ts +++ b/src/test/fixtures/profiles/call-nodes.ts @@ -49,6 +49,7 @@ export default function getProfile(): Profile { source: Array(funcNames.length).fill(null), lineNumber: Array(funcNames.length).fill(null), columnNumber: Array(funcNames.length).fill(null), + originalLocation: Array(funcNames.length).fill(null), length: funcNames.length, }; @@ -82,6 +83,7 @@ export default function getProfile(): Profile { innerWindowID: Array(frameFuncs.length).fill(null), line: Array(frameFuncs.length).fill(null), column: Array(frameFuncs.length).fill(null), + originalLocation: Array(frameFuncs.length).fill(null), length: frameFuncs.length, }; diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 428b559836..2fa0338069 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -978,6 +978,7 @@ function _buildThreadFromTextOnlyStacks( frameTable.nativeSymbol.push(nativeSymbol); frameTable.line.push(lineNumber); frameTable.column.push(null); + frameTable.originalLocation.push(null); frameIndex = frameTable.length++; } @@ -2061,6 +2062,9 @@ export function addInnerWindowIdToStacks( frameTable.nativeSymbol.push(frameTable.nativeSymbol[foundFrameIndex]); frameTable.line.push(frameTable.line[foundFrameIndex]); frameTable.column.push(frameTable.column[foundFrameIndex]); + frameTable.originalLocation.push( + frameTable.originalLocation[foundFrameIndex] + ); // And that one comes from the second tab. frameTable.innerWindowID.push(listOfOperations[1].innerWindowID); diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index 84290f4f7b..6aa4991c73 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -172,7 +172,8 @@ export function computeThreadFromRawThread( shared.resourceTable, stringTable, shared.sources, - tracedValuesBuffer + tracedValuesBuffer, + shared.originalLocation ); } @@ -688,6 +689,7 @@ export function addSourceToTable( sources.startLine.push(startLine); sources.startColumn.push(startColumn); sources.sourceMapURL.push(sourceMapURLStringIndex); + sources.content.push(null); sources.length = sources.filename.length; return sourceIndex; diff --git a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap index 71147b11fc..0cf767e180 100644 --- a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap +++ b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap @@ -87,7 +87,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -458,6 +458,50 @@ Object { 9, 3, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -565,6 +609,21 @@ Object { 13, 18, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -674,6 +733,12 @@ Object { 18, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -710,6 +775,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -1386,7 +1452,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -1757,6 +1823,50 @@ Object { 9, 3, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -1864,6 +1974,21 @@ Object { 13, 18, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -1973,6 +2098,12 @@ Object { 18, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -2009,6 +2140,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -2685,7 +2817,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -3056,6 +3188,50 @@ Object { 9, 3, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -3163,6 +3339,21 @@ Object { 13, 18, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -3272,6 +3463,12 @@ Object { 18, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -3308,6 +3505,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -3984,7 +4182,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -4355,6 +4553,50 @@ Object { 9, 3, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -4462,6 +4704,21 @@ Object { 13, 18, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -4571,6 +4828,12 @@ Object { 18, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -4607,6 +4870,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 6702059ad9..1b61a791df 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -423,7 +423,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sourceURL": "", @@ -532,6 +532,17 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -590,6 +601,17 @@ Object { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -631,6 +653,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -639,6 +667,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -2470,6 +2499,17 @@ CallTree { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -2528,6 +2568,17 @@ CallTree { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -2585,6 +2636,12 @@ CallTree { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "pausedRanges": Array [], "pid": "0", "processName": undefined, @@ -2644,6 +2701,7 @@ CallTree { }, "showMarkersInTimeline": undefined, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -2829,6 +2887,17 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -2887,6 +2956,17 @@ Object { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -2944,6 +3024,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "pausedRanges": Array [], "pid": "0", "processName": undefined, @@ -3003,6 +3089,7 @@ Object { }, "showMarkersInTimeline": undefined, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -3266,6 +3353,17 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -3324,6 +3422,17 @@ Object { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -3381,6 +3490,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "pausedRanges": Array [], "pid": "0", "processName": undefined, @@ -3428,6 +3543,7 @@ Object { }, "showMarkersInTimeline": undefined, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -3611,6 +3727,17 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -3669,6 +3796,17 @@ Object { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -3726,6 +3864,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "pausedRanges": Array [], "pid": "0", "processName": undefined, @@ -3785,6 +3929,7 @@ Object { }, "showMarkersInTimeline": undefined, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -3968,6 +4113,17 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -4026,6 +4182,17 @@ Object { 7, 8, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -4083,6 +4250,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "pausedRanges": Array [], "pid": "0", "processName": undefined, @@ -4142,6 +4315,7 @@ Object { }, "showMarkersInTimeline": undefined, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index 6c0983e811..ffcd31caf7 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -591,7 +591,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -22118,2697 +22118,11 @@ Object { null, null, ], - "subcategory": Array [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + "originalLocation": Array [ null, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, null, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, null, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, null, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ], - }, - "funcTable": Object { - "columnNumber": Array [ null, null, null, @@ -26753,3161 +24067,6 @@ Object { null, null, null, - ], - "isJS": Array [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - ], - "length": 1944, - "lineNumber": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, null, null, null, @@ -30647,947 +24806,9476 @@ Object { null, null, ], - "name": Array [ + "subcategory": Array [ 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132, - 133, - 134, - 135, - 136, - 137, - 138, - 139, - 140, - 141, - 142, - 143, - 144, - 145, - 146, - 147, - 148, - 149, - 150, - 151, - 152, - 153, - 154, - 155, - 156, - 157, - 158, - 159, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 215, - 216, - 217, - 218, - 219, - 220, - 221, - 222, - 223, - 224, - 225, - 226, - 227, - 228, - 229, - 230, - 231, - 232, - 233, - 234, - 235, - 236, - 237, - 238, - 239, - 240, - 241, - 242, - 243, - 244, - 245, - 246, - 247, - 248, - 249, - 250, - 251, - 252, - 253, - 254, - 255, - 256, - 257, - 258, - 259, - 260, - 261, - 262, - 263, - 264, - 265, - 266, - 267, - 268, - 269, - 270, - 271, - 272, - 273, - 274, - 275, - 276, - 277, - 278, - 279, - 280, - 281, - 282, - 283, - 284, - 285, - 286, - 287, - 288, - 289, - 290, - 291, - 292, - 293, - 294, - 295, - 296, - 297, - 298, - 299, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308, - 309, - 310, - 311, - 312, - 313, - 314, - 315, - 316, - 317, - 318, - 319, - 320, - 321, - 322, - 323, - 324, - 325, - 326, - 327, - 328, - 329, - 330, - 331, - 332, - 333, - 334, - 335, - 336, - 337, - 338, - 339, - 340, - 341, - 342, - 343, - 344, - 345, - 346, - 347, - 348, - 349, - 350, - 351, - 352, - 353, - 354, - 355, - 356, - 357, - 358, - 359, - 360, - 361, - 362, - 363, - 364, - 365, - 366, - 367, - 368, - 369, - 370, - 371, - 372, - 373, - 374, - 375, - 376, - 377, - 378, - 379, - 380, - 381, - 382, - 383, - 384, - 385, - 386, - 387, - 388, - 389, - 390, - 391, - 392, - 393, - 394, - 395, - 396, - 397, - 398, - 399, - 400, - 401, - 402, - 403, - 404, - 405, - 406, - 407, - 408, - 409, - 410, - 411, - 412, - 413, - 414, - 415, - 416, - 417, - 418, - 419, - 420, - 421, - 422, - 423, - 424, - 425, - 426, - 427, - 428, - 429, - 430, - 431, - 432, - 433, - 434, - 435, - 436, - 437, - 438, - 439, - 440, - 441, - 442, - 443, - 444, - 445, - 446, - 447, - 448, - 449, - 450, - 451, - 452, - 453, - 454, - 455, - 456, - 457, - 458, - 459, - 460, - 461, - 462, - 463, - 464, - 465, - 466, - 467, - 468, - 469, - 470, - 471, - 472, - 473, - 474, - 475, - 476, - 477, - 478, - 479, - 480, - 481, - 482, - 483, - 484, - 485, - 486, - 487, - 488, - 489, - 490, - 491, - 492, - 493, - 494, - 495, - 496, - 497, - 498, - 499, - 500, - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520, - 521, - 522, - 523, - 524, - 525, - 526, - 527, - 528, - 529, - 530, - 531, - 532, - 533, - 534, - 535, - 536, - 537, - 538, - 539, - 540, - 541, - 542, - 543, - 544, - 545, - 546, - 547, - 548, - 549, - 550, - 551, - 552, - 553, - 554, - 555, - 556, - 557, - 558, - 559, - 560, - 561, - 562, - 563, - 564, - 565, - 566, - 567, - 568, - 569, - 570, - 571, - 572, - 573, - 574, - 575, - 576, - 577, - 578, - 579, - 580, - 581, - 582, - 583, - 584, - 585, - 586, - 587, - 588, - 589, - 590, - 591, - 592, - 593, - 594, - 595, - 596, - 597, - 598, - 599, - 600, - 601, - 602, - 603, - 604, - 605, - 606, - 607, - 608, - 609, - 610, - 611, - 612, - 613, - 614, - 615, - 616, - 617, - 618, - 619, - 620, - 621, - 622, - 623, - 624, - 625, - 626, - 627, - 628, - 629, - 630, - 631, - 632, - 633, - 634, - 635, - 636, - 637, - 638, - 639, - 640, - 641, - 642, - 643, - 644, - 645, - 646, - 647, - 648, - 649, - 650, - 651, - 652, - 653, - 654, - 655, - 656, - 657, - 658, - 659, - 660, - 661, - 662, - 663, - 664, - 665, - 666, - 667, - 668, - 669, - 670, - 671, - 672, - 673, - 674, - 675, - 676, - 677, - 678, - 679, - 680, - 681, - 682, - 683, - 684, - 685, - 686, - 687, - 688, - 689, - 690, - 691, - 692, - 693, - 694, - 695, - 696, - 697, - 698, - 699, - 700, - 701, - 702, - 703, - 704, - 705, - 706, - 707, - 708, - 709, - 710, - 711, - 712, - 713, - 714, - 715, - 716, - 717, - 718, - 719, - 720, - 721, - 722, - 723, - 724, - 725, - 726, - 727, - 728, - 729, - 730, - 731, - 732, - 733, - 734, - 735, - 736, - 737, - 738, - 739, - 740, - 741, - 742, - 743, - 744, - 745, - 746, - 747, - 748, - 749, - 750, - 751, - 752, - 753, - 754, - 755, - 756, - 757, - 758, - 759, - 760, - 761, - 762, - 763, - 764, - 765, - 766, - 767, - 768, - 769, - 770, - 771, - 772, - 773, - 774, - 775, - 776, - 777, - 778, - 779, - 780, - 781, - 782, - 783, - 784, - 785, - 786, - 787, - 788, - 789, - 790, - 791, - 792, - 793, - 794, - 795, - 796, - 797, - 798, - 799, - 800, - 801, - 802, - 803, - 804, - 805, - 806, - 807, - 808, - 809, - 810, - 811, - 812, - 813, - 814, - 815, - 816, - 817, - 818, - 819, - 820, - 821, - 822, - 823, - 824, - 825, - 826, - 827, - 828, - 829, - 830, - 831, - 832, - 833, - 834, - 835, - 836, - 837, - 838, - 839, - 840, - 841, - 842, - 843, - 844, - 845, - 846, - 847, - 848, - 849, - 850, - 851, - 852, - 853, - 854, - 855, - 856, - 857, - 858, - 859, - 860, - 861, - 862, - 863, - 864, - 865, - 866, - 867, - 868, - 869, - 870, - 871, - 872, - 873, - 874, - 875, - 876, - 877, - 878, - 879, - 880, - 881, - 882, - 883, - 884, - 885, - 886, - 887, - 888, - 889, - 890, - 891, - 892, - 893, - 894, - 895, - 896, - 897, - 898, - 899, - 900, - 901, - 902, - 903, - 904, - 905, - 906, - 907, - 908, - 909, - 910, - 911, - 912, - 913, - 914, - 915, - 916, - 917, - 918, - 919, - 920, - 921, - 922, - 923, - 924, - 925, - 926, - 927, - 928, - 929, - 930, - 931, - 932, - 933, - 934, - 935, - 936, - 937, - 938, - 939, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + }, + "funcTable": Object { + "columnNumber": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "isJS": Array [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ], + "length": 1944, + "lineNumber": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "name": Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + 297, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308, + 309, + 310, + 311, + 312, + 313, + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374, + 375, + 376, + 377, + 378, + 379, + 380, + 381, + 382, + 383, + 384, + 385, + 386, + 387, + 388, + 389, + 390, + 391, + 392, + 393, + 394, + 395, + 396, + 397, + 398, + 399, + 400, + 401, + 402, + 403, + 404, + 405, + 406, + 407, + 408, + 409, + 410, + 411, + 412, + 413, + 414, + 415, + 416, + 417, + 418, + 419, + 420, + 421, + 422, + 423, + 424, + 425, + 426, + 427, + 428, + 429, + 430, + 431, + 432, + 433, + 434, + 435, + 436, + 437, + 438, + 439, + 440, + 441, + 442, + 443, + 444, + 445, + 446, + 447, + 448, + 449, + 450, + 451, + 452, + 453, + 454, + 455, + 456, + 457, + 458, + 459, + 460, + 461, + 462, + 463, + 464, + 465, + 466, + 467, + 468, + 469, + 470, + 471, + 472, + 473, + 474, + 475, + 476, + 477, + 478, + 479, + 480, + 481, + 482, + 483, + 484, + 485, + 486, + 487, + 488, + 489, + 490, + 491, + 492, + 493, + 494, + 495, + 496, + 497, + 498, + 499, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 509, + 510, + 511, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 529, + 530, + 531, + 532, + 533, + 534, + 535, + 536, + 537, + 538, + 539, + 540, + 541, + 542, + 543, + 544, + 545, + 546, + 547, + 548, + 549, + 550, + 551, + 552, + 553, + 554, + 555, + 556, + 557, + 558, + 559, + 560, + 561, + 562, + 563, + 564, + 565, + 566, + 567, + 568, + 569, + 570, + 571, + 572, + 573, + 574, + 575, + 576, + 577, + 578, + 579, + 580, + 581, + 582, + 583, + 584, + 585, + 586, + 587, + 588, + 589, + 590, + 591, + 592, + 593, + 594, + 595, + 596, + 597, + 598, + 599, + 600, + 601, + 602, + 603, + 604, + 605, + 606, + 607, + 608, + 609, + 610, + 611, + 612, + 613, + 614, + 615, + 616, + 617, + 618, + 619, + 620, + 621, + 622, + 623, + 624, + 625, + 626, + 627, + 628, + 629, + 630, + 631, + 632, + 633, + 634, + 635, + 636, + 637, + 638, + 639, + 640, + 641, + 642, + 643, + 644, + 645, + 646, + 647, + 648, + 649, + 650, + 651, + 652, + 653, + 654, + 655, + 656, + 657, + 658, + 659, + 660, + 661, + 662, + 663, + 664, + 665, + 666, + 667, + 668, + 669, + 670, + 671, + 672, + 673, + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 681, + 682, + 683, + 684, + 685, + 686, + 687, + 688, + 689, + 690, + 691, + 692, + 693, + 694, + 695, + 696, + 697, + 698, + 699, + 700, + 701, + 702, + 703, + 704, + 705, + 706, + 707, + 708, + 709, + 710, + 711, + 712, + 713, + 714, + 715, + 716, + 717, + 718, + 719, + 720, + 721, + 722, + 723, + 724, + 725, + 726, + 727, + 728, + 729, + 730, + 731, + 732, + 733, + 734, + 735, + 736, + 737, + 738, + 739, + 740, + 741, + 742, + 743, + 744, + 745, + 746, + 747, + 748, + 749, + 750, + 751, + 752, + 753, + 754, + 755, + 756, + 757, + 758, + 759, + 760, + 761, + 762, + 763, + 764, + 765, + 766, + 767, + 768, + 769, + 770, + 771, + 772, + 773, + 774, + 775, + 776, + 777, + 778, + 779, + 780, + 781, + 782, + 783, + 784, + 785, + 786, + 787, + 788, + 789, + 790, + 791, + 792, + 793, + 794, + 795, + 796, + 797, + 798, + 799, + 800, + 801, + 802, + 803, + 804, + 805, + 806, + 807, + 808, + 809, + 810, + 811, + 812, + 813, + 814, + 815, + 816, + 817, + 818, + 819, + 820, + 821, + 822, + 823, + 824, + 825, + 826, + 827, + 828, + 829, + 830, + 831, + 832, + 833, + 834, + 835, + 836, + 837, + 838, + 839, + 840, + 841, + 842, + 843, + 844, + 845, + 846, + 847, + 848, + 849, + 850, + 851, + 852, + 853, + 854, + 855, + 856, + 857, + 858, + 859, + 860, + 861, + 862, + 863, + 864, + 865, + 866, + 867, + 868, + 869, + 870, + 871, + 872, + 873, + 874, + 875, + 876, + 877, + 878, + 879, + 880, + 881, + 882, + 883, + 884, + 885, + 886, + 887, + 888, + 889, + 890, + 891, + 892, + 893, + 894, + 895, + 896, + 897, + 898, + 899, + 900, + 901, + 902, + 903, + 904, + 905, + 906, + 907, + 908, + 909, + 910, + 911, + 912, + 913, + 914, + 915, + 916, + 917, + 918, + 919, + 920, + 921, + 922, + 923, + 924, + 925, + 926, + 927, + 928, + 929, + 930, + 931, + 932, + 933, + 934, + 935, + 936, + 937, + 938, + 939, 940, 941, 942, @@ -32593,6 +35281,1952 @@ Object { 1942, 1943, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -38439,6 +43073,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -38447,6 +43087,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -79192,7 +83833,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -111698,8 +116339,5422 @@ Object { 0, 0, ], - "length": 5412, - "line": Array [ + "length": 5412, + "line": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "nativeSymbol": Array [ null, null, null, @@ -117113,7 +127168,7 @@ Object { null, null, ], - "nativeSymbol": Array [ + "originalLocation": Array [ null, null, null, @@ -142560,6 +152615,3660 @@ Object { 3650, 3651, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -153530,6 +167239,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -153538,6 +167253,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -315562,7 +329278,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -315775,6 +329491,34 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -315914,6 +329658,33 @@ Object { 26, 27, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -316003,6 +329774,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ 4, @@ -316019,6 +329796,9 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + ], "filename": Array [ 2, ], @@ -350479,7 +364259,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -350692,6 +364472,34 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -350831,6 +364639,33 @@ Object { 26, 27, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -350920,6 +364755,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ 4, @@ -350936,6 +364777,9 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + ], "filename": Array [ 2, ], @@ -385375,7 +399219,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -385735,6 +399579,55 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -385959,6 +399852,49 @@ Object { 20, 35, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -386096,6 +400032,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ 5, @@ -386112,6 +400054,11 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + null, + null, + ], "filename": Array [ 3, 7, @@ -387980,7 +401927,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -388002,6 +401949,7 @@ Object { "length": 0, "line": Array [], "nativeSymbol": Array [], + "originalLocation": Array [], "subcategory": Array [], }, "funcTable": Object { @@ -388010,6 +401958,7 @@ Object { "length": 0, "lineNumber": Array [], "name": Array [], + "originalLocation": Array [], "relevantForJS": Array [], "resource": Array [], "source": Array [], @@ -388021,6 +401970,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -388029,6 +401984,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -389809,7 +403765,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 355035987.653, @@ -390988,6 +404944,172 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -391633,6 +405755,125 @@ Object { 136, 138, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -391998,6 +406239,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -392190,6 +406437,53 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "filename": Array [ 3, 5, @@ -393482,7 +407776,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -395128,6 +409422,239 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -395940,6 +410467,150 @@ Object { 117, 118, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -396380,6 +411051,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -396484,6 +411161,31 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "filename": Array [ 2, 4, @@ -398697,7 +413399,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -399057,6 +413759,55 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -399281,6 +414032,49 @@ Object { 20, 35, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -399418,6 +414212,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ 5, @@ -399434,6 +414234,11 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + null, + null, + ], "filename": Array [ 3, 7, @@ -399739,7 +414544,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -401394,6 +416199,210 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -401897,6 +416906,80 @@ Object { 151, 153, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -402127,6 +417210,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -402187,6 +417276,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -403614,7 +418704,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -408533,6 +423623,618 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -411596,6 +427298,618 @@ Object { 1227, 1229, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -413440,6 +429754,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -413572,6 +429892,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -418512,7 +434833,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -420383,6 +436704,237 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -421541,6 +438093,237 @@ Object { 458, 460, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -422242,6 +439025,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -422278,6 +439067,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -424719,7 +441509,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -431174,6 +447964,810 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -434561,6 +452155,651 @@ Object { 1313, 1315, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -436504,6 +454743,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -436604,6 +454849,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -441164,7 +459410,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -452252,7 +470498,2223 @@ Object { 0, 0, ], - "innerWindowID": Array [ + "innerWindowID": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "length": 2213, + "line": Array [ null, null, null, @@ -454467,8 +474929,7 @@ Object { null, null, ], - "length": 2213, - "line": Array [ + "nativeSymbol": Array [ null, null, null, @@ -456683,7 +477144,7 @@ Object { null, null, ], - "nativeSymbol": Array [ + "originalLocation": Array [ null, null, null, @@ -469976,6 +490437,2221 @@ Object { 2269, 2270, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -476629,6 +499305,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -476901,6 +499583,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -498819,7 +521502,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -509227,7 +531910,2087 @@ Object { 0, 0, ], - "innerWindowID": Array [ + "innerWindowID": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "length": 2077, + "line": Array [ null, null, null, @@ -511306,8 +536069,7 @@ Object { null, null, ], - "length": 2077, - "line": Array [ + "nativeSymbol": Array [ null, null, null, @@ -513386,7 +538148,7 @@ Object { null, null, ], - "nativeSymbol": Array [ + "originalLocation": Array [ null, null, null, @@ -525863,6 +550625,2085 @@ Object { 2110, 2111, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -532108,6 +558949,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -532276,6 +559123,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -561079,7 +587927,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "target/debug/examples/work_log (dhat)", "sourceURL": "", @@ -564549,6 +591397,438 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -565844,6 +593124,221 @@ Object { 86, 51, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -566497,6 +593992,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -566505,6 +594006,50 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "filename": Array [ 0, 3, @@ -569632,7 +597177,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Flamegraph", "sourceURL": "", @@ -570894,6 +598439,162 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -571677,6 +599378,162 @@ Object { 152, 153, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -572153,6 +600010,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -572161,6 +600024,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -877518,7 +905382,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Flamegraph", "sourceURL": "", @@ -877588,6 +905452,13 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + ], "subcategory": Array [ 0, 0, @@ -877626,6 +905497,13 @@ Object { 3, 4, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -877655,6 +905533,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [], "length": 0, @@ -877663,6 +905547,7 @@ Object { "type": Array [], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index e85d4f0300..bbeb4fc3a2 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -40,7 +40,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -1695,6 +1695,210 @@ Object { null, null, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -2198,6 +2402,80 @@ Object { 151, 153, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -2428,6 +2706,12 @@ Object { "libIndex": Array [], "name": Array [], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -2488,6 +2772,7 @@ Object { ], }, "sources": Object { + "content": Array [], "filename": Array [], "id": Array [], "length": 0, @@ -7358,7 +7643,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -7553,6 +7838,29 @@ Object { 3, 2, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -7615,6 +7923,15 @@ Object { 6, 7, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -7670,6 +7987,12 @@ Object { 7, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -7694,6 +8017,9 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + ], "filename": Array [ 10, ], @@ -8693,7 +9019,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -8904,6 +9230,31 @@ Object { 3, 2, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -8976,6 +9327,17 @@ Object { 14, 15, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -9037,6 +9399,12 @@ Object { 8, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -9061,6 +9429,9 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + ], "filename": Array [ 11, ], @@ -10163,7 +10534,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 62, + "preprocessedProfileVersion": 63, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -10400,6 +10771,31 @@ Object { 3, 2, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "subcategory": Array [ null, null, @@ -10472,6 +10868,17 @@ Object { 15, 16, ], + "originalLocation": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], "relevantForJS": Array [ false, false, @@ -10533,6 +10940,12 @@ Object { 9, ], }, + "originalLocation": Object { + "column": Array [], + "length": 0, + "line": Array [], + "source": Array [], + }, "resourceTable": Object { "host": Array [ null, @@ -10557,6 +10970,9 @@ Object { ], }, "sources": Object { + "content": Array [ + null, + ], "filename": Array [ 12, ], diff --git a/src/test/unit/merge-compare.test.ts b/src/test/unit/merge-compare.test.ts index a68fa2462f..7c0917ca91 100644 --- a/src/test/unit/merge-compare.test.ts +++ b/src/test/unit/merge-compare.test.ts @@ -985,4 +985,121 @@ describe('mergeProfilesForDiffing with source tables', function () { singleProfile.profile.shared.sources ); }); + + it('should merge originalLocation tables and remap funcTable / frameTable references', function () { + const profileA = getProfileFromTextSamples(` + A[file:bundle-a.js] + `); + const profileB = getProfileFromTextSamples(` + B[file:bundle-b.js] + `); + + // Pre-populate originalLocation and the func/frame columns that reference it. + // bundle-a.js source is index 0 in each per-profile table (sources from + // getProfileFromTextSamples have null id but a filename string). + const sharedA = profileA.profile.shared; + sharedA.originalLocation.source.push(0); + sharedA.originalLocation.line.push(11); + sharedA.originalLocation.column.push(22); + sharedA.originalLocation.length = 1; + sharedA.funcTable.originalLocation[0] = 0; + sharedA.frameTable.originalLocation[0] = 0; + + const sharedB = profileB.profile.shared; + sharedB.originalLocation.source.push(0); + sharedB.originalLocation.line.push(33); + sharedB.originalLocation.column.push(44); + sharedB.originalLocation.length = 1; + sharedB.funcTable.originalLocation[0] = 0; + sharedB.frameTable.originalLocation[0] = 0; + + const profileState = stateFromLocation({ + pathname: '/public/fakehash1/', + search: '?thread=0&v=3', + hash: '', + }); + + const { profile: mergedProfile } = mergeProfilesForDiffing( + [profileA.profile, profileB.profile], + [profileState, profileState] + ); + + const { funcTable, frameTable, originalLocation, sources } = + mergedProfile.shared; + const stringTable = StringTable.withBackingArray( + mergedProfile.shared.stringArray + ); + + // Both inputs' originalLocation rows survive the merge (no dedup). + expect(originalLocation.length).toBe(2); + + // source indices are remapped through the merged sources table. + for (let i = 0; i < originalLocation.length; i++) { + const src = originalLocation.source[i]; + expect(src).not.toBeNull(); + expect(src).toBeGreaterThanOrEqual(0); + expect(src).toBeLessThan(sources.length); + } + + // Each input's funcTable.originalLocation[0] now points at the corresponding + // remapped row, and the remapped row carries the correct original line. + const funcAName = stringTable.indexForString('A'); + const funcBName = stringTable.indexForString('B'); + const funcAIndex = funcTable.name.indexOf(funcAName); + const funcBIndex = funcTable.name.indexOf(funcBName); + expect(funcAIndex).toBeGreaterThanOrEqual(0); + expect(funcBIndex).toBeGreaterThanOrEqual(0); + + const funcAOriginalLocationIdx = funcTable.originalLocation[funcAIndex]; + const funcBOriginalLocationIdx = funcTable.originalLocation[funcBIndex]; + expect(funcAOriginalLocationIdx).not.toBeNull(); + expect(funcBOriginalLocationIdx).not.toBeNull(); + expect(originalLocation.line[ensureExists(funcAOriginalLocationIdx)]).toBe( + 11 + ); + expect(originalLocation.line[ensureExists(funcBOriginalLocationIdx)]).toBe( + 33 + ); + + // The frame columns are populated for every row (catches the original bug + // where the column was left empty and any index access returned undefined). + for (let i = 0; i < frameTable.length; i++) { + const v = frameTable.originalLocation[i]; + expect(v === null || v >= 0).toBe(true); + } + for (let i = 0; i < funcTable.length; i++) { + const v = funcTable.originalLocation[i]; + expect(v === null || v >= 0).toBe(true); + } + }); + + it('should populate empty originalLocation columns on funcTable and frameTable when no symbolication ran', function () { + const profileA = getProfileFromTextSamples(`A[file:a.js]`); + const profileB = getProfileFromTextSamples(`B[file:b.js]`); + + const profileState = stateFromLocation({ + pathname: '/public/fakehash1/', + search: '?thread=0&v=3', + hash: '', + }); + + const { profile: mergedProfile } = mergeProfilesForDiffing( + [profileA.profile, profileB.profile], + [profileState, profileState] + ); + + const { funcTable, frameTable, originalLocation } = mergedProfile.shared; + + // Even without any symbolicated entries on the inputs, the columns must + // be filled (not undefined) so downstream `x !== null` checks work. + expect(originalLocation.length).toBe(0); + expect(funcTable.originalLocation).toHaveLength(funcTable.length); + expect(frameTable.originalLocation).toHaveLength(frameTable.length); + for (const v of funcTable.originalLocation) { + expect(v).toBeNull(); + } + for (const v of frameTable.originalLocation) { + expect(v).toBeNull(); + } + }); }); diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index cc641fbe78..2009afd63b 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -33,6 +33,7 @@ import type { SourceTable, IndexIntoSourceTable, CounterDisplayConfig, + SourceLocationTable, } from './profile'; import type { IndexedArray } from './utils'; import type { BitSet } from '../utils/bitset'; @@ -109,6 +110,8 @@ export type Thread = { // Sources for profiles are collected into a single table, containing file sources // with their UUIDs and filenames. sources: SourceTable; + // Source map symbolication results, copied from profile.shared.originalLocation. + originalLocation: SourceLocationTable; // The stack samples collected for this thread. This field is different from // RawThread in that the `time` column is always present. samples: SamplesTable; diff --git a/src/types/profile.ts b/src/types/profile.ts index 04d8a2ee79..b0888e04d0 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -23,6 +23,7 @@ export type IndexIntoNativeSymbolTable = number; export type IndexIntoCategoryList = number; export type IndexIntoSubcategoryListForCategory = number; export type IndexIntoSourceTable = number; +export type IndexIntoSourceLocationTable = number; export type ThreadIndex = number; // The Tid is most often a number. However in some cases such as merged profiles // we could generate a string. @@ -296,6 +297,12 @@ export type FrameTable = { line: (number | null)[]; column: (number | null)[]; + + // Index into the originalLocation table, or null if not source-mapped. + // Points to the original source file, line, and column for this frame's + // execution point. + originalLocation: Array; + length: number; }; @@ -342,6 +349,11 @@ export type FuncTable = { lineNumber: Array; columnNumber: Array; + // Index into the originalLocation table, or null if not source-mapped. + // Points to the original source file, line, and column for this function's + // definition. + originalLocation: Array; + length: number; }; @@ -980,6 +992,28 @@ export type SourceTable = { startLine: Array; startColumn: Array; sourceMapURL: Array; + // Original source file contents from source map sourcesContent, or null if + // not available. Stored for offline source view when a profile is shared. + content: Array; +}; + +/** + * Table holding source locations, currently populated from source map + * symbolication. Each row stores a (source, line, column) triple. Frames and + * funcs index into this table via their `originalLocation` column to record + * the pre-compilation counterpart to their inline (generated) line/column. + */ +export type SourceLocationTable = { + // Source file index. + // For funcs: the file where the function is defined. + // For frames: the source file for the execution point. Usually matches the + // func's source, but can differ for inlined code. + source: IndexIntoSourceTable[]; + // 1-based line number. + line: number[]; + // 1-based column number. + column: number[]; + length: number; }; export type RawProfileSharedData = { @@ -994,6 +1028,8 @@ export type RawProfileSharedData = { // Optional sources table for JS source UUID to URL mapping. // Added for UUID-based source fetching. sources: SourceTable; + // Source map symbolication results, shared across all threads. + originalLocation: SourceLocationTable; }; /** From 8b75c020294db1c88df0c8ef3c199abecabddca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:25:16 -0400 Subject: [PATCH 04/16] Add nonymous module for SpiderMonkey anonymous function names The nonymous algorithm (johnjbarton.github.io/nonymous) is how SpiderMonkey labels anonymous JS functions inside its NameFunctions.cpp pass. Adds a parser and serializer for the `/`-separated, `<`-marked name format Gecko emits, so later symbolication can produce names that match what the browser already shows. Pure utility, no callers yet. --- src/profile-logic/nonymous.ts | 64 +++++++++++++++ src/test/unit/nonymous.test.ts | 146 +++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 src/profile-logic/nonymous.ts create mode 100644 src/test/unit/nonymous.test.ts diff --git a/src/profile-logic/nonymous.ts b/src/profile-logic/nonymous.ts new file mode 100644 index 0000000000..9dcec52434 --- /dev/null +++ b/src/profile-logic/nonymous.ts @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Parse and serialize Nonymous function names as produced by SpiderMonkey's + * NameFunctions pass: + * https://searchfox.org/firefox-main/rev/1f7030c8de8f2b349c7d91d7b5a3253c109a1cc1/js/src/frontend/NameFunctions.cpp + * + * Nonymous algorithm: https://johnjbarton.github.io/nonymous/index.html + * + * The format uses '/' as a scope separator and '<' as a "contributes-to" + * marker for anonymous functions. Examples: + * "foo" - named function assigned to `foo` + * "foo<" - function contributing to `foo` (e.g. passed as arg) + * "obj.method" - method on an object + * "outer/inner" - `inner` defined inside `outer` + * "outer/inner<" - anonymous inside `outer`, contributing to `inner` + * "outer/<" - anonymous inside `outer`, no assignment context + * "i2 { + it('parses a simple identifier', () => { + expect(parseNonymousName('foo')).toEqual([ + { kind: 'named', name: 'foo', contributesTo: false }, + ]); + }); + + it('parses a contributes-to name', () => { + expect(parseNonymousName('foo<')).toEqual([ + { kind: 'named', name: 'foo', contributesTo: true }, + ]); + }); + + it('parses a bare anonymous segment', () => { + expect(parseNonymousName('<')).toEqual([{ kind: 'anonymous' }]); + }); + + it('parses a scoped name', () => { + expect(parseNonymousName('outer/inner')).toEqual([ + { kind: 'named', name: 'outer', contributesTo: false }, + { kind: 'named', name: 'inner', contributesTo: false }, + ]); + }); + + it('parses a three-level scope chain', () => { + expect(parseNonymousName('foz/baz/bay')).toEqual([ + { kind: 'named', name: 'foz', contributesTo: false }, + { kind: 'named', name: 'baz', contributesTo: false }, + { kind: 'named', name: 'bay', contributesTo: false }, + ]); + }); + + it('parses a scoped contributes-to name', () => { + expect(parseNonymousName('main/foo<')).toEqual([ + { kind: 'named', name: 'main', contributesTo: false }, + { kind: 'named', name: 'foo', contributesTo: true }, + ]); + }); + + it('parses the canonical i2 { + expect(parseNonymousName('i2 { + expect(parseNonymousName('outer/<')).toEqual([ + { kind: 'named', name: 'outer', contributesTo: false }, + { kind: 'anonymous' }, + ]); + }); + + it('parses a property chain name', () => { + expect(parseNonymousName('this.eventPool_.createObject')).toEqual([ + { + kind: 'named', + name: 'this.eventPool_.createObject', + contributesTo: false, + }, + ]); + }); + + it('parses a numeric element access name', () => { + expect(parseNonymousName('arr[0]')).toEqual([ + { kind: 'named', name: 'arr[0]', contributesTo: false }, + ]); + }); + + it('returns anonymous for an empty string', () => { + expect(parseNonymousName('')).toEqual([{ kind: 'anonymous' }]); + }); +}); + +describe('serializeNonymousName', () => { + it('serializes a simple named local', () => { + const name: NonymousName = [ + { kind: 'named', name: 'foo', contributesTo: false }, + ]; + expect(serializeNonymousName(name)).toBe('foo'); + }); + + it('serializes a contributes-to local', () => { + const name: NonymousName = [ + { kind: 'named', name: 'foo', contributesTo: true }, + ]; + expect(serializeNonymousName(name)).toBe('foo<'); + }); + + it('serializes an anonymous local', () => { + const name: NonymousName = [{ kind: 'anonymous' }]; + expect(serializeNonymousName(name)).toBe('<'); + }); + + it('serializes a scoped name', () => { + const name: NonymousName = [ + { kind: 'named', name: 'outer', contributesTo: false }, + { kind: 'named', name: 'inner', contributesTo: false }, + ]; + expect(serializeNonymousName(name)).toBe('outer/inner'); + }); + + it('round-trips all parse test cases', () => { + const cases = [ + 'foo', + 'foo<', + '<', + 'outer/inner', + 'foz/baz/bay', + 'main/foo<', + 'i2 { + const cases = [ + 'foo', + 'foo<', + 'obj.method', + 'outer/inner', + 'outer/inner<', + 'outer/<', + 'i2 Date: Mon, 11 May 2026 16:25:37 -0400 Subject: [PATCH 05/16] Add source-map scope tree parsing with @lezer/javascript Parses compiled JS with @lezer/javascript and walks the CST to build a tree of function scopes with name-mapping locations. The character offsets later probed against the source map to recover original function names. `@lezer/javascript` is already a transitive dep via `@codemirror/lang-javascript`. Pure utility, no callers yet. Tested via the new source-map-scope-tree.test.ts. --- package.json | 1 + src/profile-logic/source-map-scope-tree.ts | 730 ++++++++++++++++++++ src/test/unit/source-map-scope-tree.test.ts | 606 ++++++++++++++++ src/utils/line-offsets.ts | 46 ++ yarn.lock | 31 +- 5 files changed, 1399 insertions(+), 15 deletions(-) create mode 100644 src/profile-logic/source-map-scope-tree.ts create mode 100644 src/test/unit/source-map-scope-tree.test.ts create mode 100644 src/utils/line-offsets.ts diff --git a/package.json b/package.json index f91ef0dbee..489fa2b1ca 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", "@lezer/highlight": "^1.2.3", + "@lezer/javascript": "^1.5.4", "@streamparser/json": "^0.0.22", "@tgwf/co2": "^0.18.0", "array-move": "^3.0.1", diff --git a/src/profile-logic/source-map-scope-tree.ts b/src/profile-logic/source-map-scope-tree.ts new file mode 100644 index 0000000000..f76d9bd2b0 --- /dev/null +++ b/src/profile-logic/source-map-scope-tree.ts @@ -0,0 +1,730 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Scope tree construction from compiled JavaScript using Lezer. + * + * ## Why we need this + * + * Source maps don't expose function scopes. They're a flat list of + * generated -> original token mappings, some of which carry a `name`. + * There's no "this range belongs to function F" information, and for many + * functions there's no usable name at any single token to begin with: + * + * - For anonymous functions (`() => {}`, `function() {}`), there is no + * identifier in either source for the mapping to attach a name to. + * - For functions inferred from context (`var x = () => {}`, + * `obj.foo = function() {}`, `obj[key] = fn`), the name has to be + * recovered from surrounding AST nodes, not from the function token. + * - Even for self-named functions, the sample's position lands somewhere + * in the body, not on the identifier, so we still need to know where + * the identifier sits in order to probe the source map for it. + * + * So we reconstruct that information by parsing the compiled JS + * ourselves. The shapes we recognize and the fields we record for each + * are catalogued below, and the resolver in source-map-symbolication.ts + * consumes them. + * + * The TC39 source-map scopes proposal (ecma426, stage 3) would encode + * this scope information directly in the source map and let us skip the + * CST reconstruction entirely. Once it ships and toolchains start emitting + * it, this file can become a fallback for source maps that lack a scopes + * section. See https://github.com/tc39/ecma426/blob/main/proposals/scopes.md. + * + * ## What this file produces + * + * Parses the compiled source with @lezer/javascript, walks the resulting + * concrete syntax tree (CST), and builds a tree of function scopes. A CST + * is a syntax tree that preserves every token from the source, including + * punctuation, parentheses, and whitespace positions, unlike an abstract + * syntax tree (AST), which typically drops them. We need the CST because + * the whole point of this pass is to compute character offsets in the + * compiled source (for source-map probes), so the exact positions of + * tokens like the opening `(` or the `=>` arrow matter. + * + * Each scope carries `nameMappingLocations` (character offsets to probe + * via exact source-map lookups) plus, for inferred scopes, extra fields + * the resolver in source-map-symbolication.ts uses to recover the + * original name. + * + * ## Shapes recognized + * + * Self-named (the function/method has its own identifier): + * + * function foo() {} // astName = "foo" + * const x = function bar() {} // bar's astName = "bar" + * class C { foo() {} } // astName = "foo" + * { foo: () => {} } // astName = "foo" + * + * Inferred from a direct assignment target: + * + * var x = () => {} // probe at `x`, lhsText = "x" + * obj.foo = function() {} // probe at `foo`, lhsText = "obj.foo" + * + * Inferred through a transparent wrapper (sets `contributesTo`, which the + * resolver maps to the Nonymous `<` suffix): + * + * var observer = new IntersectionObserver(() => {}) // contributesTo = true + * var x = wrap(() => {}) // contributesTo = true + * + * Computed-member LHS (sets `computedKeyLoc` so the resolver can compose + * `${receiver}[${key}]` from two independent probes): + * + * obj[key] = function() {} + * // identifierLoc at `obj`, computedKeyLoc at `key`, lhsText = "obj[key]" + * + * Every inferred scope also records `lhsText`, the verbatim source slice of + * the assignment-target LHS. The resolver feeds it through the original + * parse of the source (from `sourcesContent`), so the un-minified member + * chain survives even when the minifier rewrote the LHS. + */ + +import { parser as lezerJsParser } from '@lezer/javascript'; + +import { bisectionRightByKey } from '../utils/bisect'; + +// Derive SyntaxNode from the parser to avoid version conflicts with nested +// @lezer/common copies in node_modules (they have incompatible private fields). +type SyntaxNode = ReturnType['topNode']; + +export type FunctionScope = { + // Character offsets in the compiled source, inclusive start / exclusive end. + start: number; + end: number; + // Ordered list of character offsets to probe with an exact source-map lookup. + // The first probe that yields a named entry wins. + nameMappingLocations: number[]; + // Fallback name derived from the AST (the compiled identifier, e.g. `foo`). + // null for anonymous functions and arrow functions. + astName: string | null; + kind: 'function' | 'arrow'; + // True when the function's inferred name came through a wrapping + // `call(...)` / `new C(...)`, i.e. the function "contributes to" the + // assignment target rather than being it. Emits the Nonymous `<` suffix + // (e.g. `outer/observer<` for `var observer = new C(() => {})`). + contributesTo: boolean; + // Set for scopes inferred from a computed-member assignment `obj[key] = fn`: + // the char offset of the bracket-key expression. Used to compose a compound + // `receiver[key]` name from two independent source-map probes, matching + // SpiderMonkey's output where the function's name is the literal source + // text of the LHS (e.g. `obj[key]`). Only consulted as a fallback when the + // original source isn't available in `sourcesContent`. Otherwise `lhsText` + // wins. + computedKeyLoc: number | null; + // Verbatim source text of the assignment-target LHS for inferred scopes. + // For a scope built from this scope-tree's source, that's just the slice + // of `text` between the LHS node's `from`/`to`. The resolver uses it via + // the *original* parse of the source (read from the source map's + // `sourcesContent`), so the name is whatever the developer wrote + // (`Watcher.prototype.run`, `this.eventPool_.createObject`, `obj[key]`, + // etc.), independent of how the minifier rewrote the LHS. + lhsText: string | null; + children: FunctionScope[]; +}; + +// --------------------------------------------------------------------------- +// Mapping-location helpers +// --------------------------------------------------------------------------- + +function _mappingLocationsForFunction( + node: SyntaxNode, + text: string +): number[] { + const locations: number[] = []; + const nameNode = node.getChild('VariableDefinition'); + if (nameNode) { + locations.push(nameNode.from); + } + const paramList = node.getChild('ParamList'); + if (paramList && text[paramList.from] === '(') { + locations.push(paramList.from); + } + return locations; +} + +function _mappingLocationsForArrow(node: SyntaxNode, text: string): number[] { + const locations: number[] = []; + + const paramList = node.getChild('ParamList'); + if (paramList && text[paramList.from] === '(') { + locations.push(paramList.from); + } + + const arrow = node.getChild('Arrow'); + if (arrow) { + locations.push(arrow.from); + } + + return locations; +} + +function _keyMappingLocation(node: SyntaxNode): number[] { + const key = + node.getChild('PropertyDefinition') ?? + node.getChild('PrivatePropertyDefinition'); + return key ? [key.from] : []; +} + +function _astNameForKey(node: SyntaxNode, text: string): string | null { + const key = + node.getChild('PropertyDefinition') ?? + node.getChild('PrivatePropertyDefinition'); + if (!key) { + return null; + } + // PrivatePropertyDefinition spans `#name` including the `#`. + return text.slice(key.from, key.to); +} + +/** + * Describes the assignment target of an AssignmentExpression / VariableDeclarator + * for the purpose of inferring a function name from it. + * + * `identifierLoc` is the char offset of an identifier to probe in the source + * map for the original name (the variable, property name, or, for computed + * member access, the receiver expression). + * + * `computedKeyLoc` is set for `obj[key] = fn`: the offset of the bracket-key + * expression. The resolver probes it separately and composes + * `${receiver}[${key}]` so the assigned-to expression survives symbolication. + * + * `lhsText` is the verbatim source slice of the LHS (e.g. `Foo.prototype.bar`, + * `obj[key]`, `foo`). The resolver prefers it when looking up the function in + * the original source. + */ +type LhsContext = { + identifierLoc: number; + computedKeyLoc: number | null; + lhsText: string | null; +}; + +function _lhsContextFromAssignment( + node: SyntaxNode, + text: string +): LhsContext | null { + const lhs = node.firstChild; + if (!lhs) { + return null; + } + const lhsText = text.slice(lhs.from, lhs.to); + if (lhs.name === 'VariableName') { + return { identifierLoc: lhs.from, computedKeyLoc: null, lhsText }; + } + if (lhs.name === 'MemberExpression') { + const prop = lhs.getChild('PropertyName'); + if (prop) { + return { identifierLoc: prop.from, computedKeyLoc: null, lhsText }; + } + // Computed access `receiver[key]`. The receiver's leading identifier is + // used for the main probe, and the key expression for the compound-name + // probe (so we can compose `${receiver}[${key}]` from two probes). + const keyLoc = _computedMemberKeyLoc(lhs); + if (keyLoc !== null) { + return { identifierLoc: lhs.from, computedKeyLoc: keyLoc, lhsText }; + } + } + return null; +} + +/** + * For a MemberExpression with computed access (`receiver[keyExpr]`), + * return the char offset of the key expression. Returns null for dotted + * accesses or unexpected shapes. + */ +function _computedMemberKeyLoc(memberExpr: SyntaxNode): number | null { + let pastBracket = false; + for (let c = memberExpr.firstChild; c; c = c.nextSibling) { + if (c.name === '[') { + pastBracket = true; + continue; + } + if (c.name === ']') { + return null; + } + if (pastBracket) { + return c.from; + } + } + return null; +} + +/** + * Create a FunctionScope for an anonymous (or arrow) function node whose name + * must be inferred from surrounding AST context (VariableDeclarator, assignment + * target, wrap-pattern call argument). + * + * `lhs.identifierLoc` is appended LAST to `nameMappingLocations` so the source + * map is queried for the original identifier name only after all of the + * function's own probes have failed. Using it last prevents a broad mapping at + * the identifier from overriding a more precise name found at the function's + * paren/arrow position. + * + * `contributesTo` reflects whether the inference passed through a wrapping + * call/`new` (i.e. the function contributes to the target rather than being + * it). It maps directly to the Nonymous `<` suffix. + * + * `astName` is intentionally left null. If the identifier probe also fails we + * have no meaningful fallback name. The compiled variable name is a minified + * identifier and using it would produce incorrect names in the profiler. In + * that case the function keeps its Gecko-assigned name unchanged. + */ +function _pushInferredScope( + funcNode: SyntaxNode, + text: string, + parentChildren: FunctionScope[], + lhs: LhsContext, + contributesTo: boolean +): void { + const funcLocations = + funcNode.name === 'ArrowFunction' + ? _mappingLocationsForArrow(funcNode, text) + : _mappingLocationsForFunction(funcNode, text); + const scope: FunctionScope = { + start: funcNode.from, + end: funcNode.to, + nameMappingLocations: [...funcLocations, lhs.identifierLoc], + astName: null, + kind: funcNode.name === 'ArrowFunction' ? 'arrow' : 'function', + contributesTo, + computedKeyLoc: lhs.computedKeyLoc, + lhsText: lhs.lhsText, + children: [], + }; + parentChildren.push(scope); + _walkChildren(funcNode, text, scope.children); +} + +/** + * Process the RHS of an assignment-like construct (a VariableDeclarator's + * init or an AssignmentExpression's RHS), inferring `identifierLoc` for any + * anonymous function / arrow argument reached through "transparent" wrapping + * expressions. + * + * In the examples below, `() => {}` stands in for any anonymous function or + * arrow function. + * + * Recognised wrappers: + * - ParenthesizedExpression - `(() => {})` + * - CallExpression - `wrap(() => {})` / `wrap(f1, f2)` + * - NewExpression - `new Class(() => {})` + * + * `throughWrapper` tracks whether the traversal has descended through a + * Call/New: that's what distinguishes "fn IS the target" (`var x = () => {}`, + * `obj.foo = () => {}`, `obj[key] = () => {}`) from "fn CONTRIBUTES TO the + * target" (`var x = wrap(() => {})`, `var x = new C(() => {})`). It maps + * directly to the Nonymous `<` suffix on the resulting scope. + * + * Other nested function literals (e.g. inside the wrapper's body or in + * non-argument positions) are processed normally with no inference. + */ +function _processInitExpr( + initNode: SyntaxNode, + text: string, + parentChildren: FunctionScope[], + lhs: LhsContext, + throughWrapper: boolean +): void { + switch (initNode.name) { + case 'ArrowFunction': + _pushInferredScope(initNode, text, parentChildren, lhs, throughWrapper); + return; + case 'FunctionExpression': { + // Only infer a name for truly anonymous functions. Named function + // expressions (`const foo = function bar() {}`) keep their `bar` name. + const isAnon = initNode.getChild('VariableDefinition') === null; + if (isAnon) { + _pushInferredScope(initNode, text, parentChildren, lhs, throughWrapper); + } else { + _processNode(initNode, text, parentChildren); + } + return; + } + case 'ParenthesizedExpression': { + // Parens are pure-syntactic. Preserve throughWrapper as-is. + for (let c = initNode.firstChild; c; c = c.nextSibling) { + if (c.name === '(' || c.name === ')') { + continue; + } + _processInitExpr(c, text, parentChildren, lhs, throughWrapper); + } + return; + } + case 'CallExpression': + case 'NewExpression': { + // For each child of the call: if it's the argument list, recurse into + // each argument with inference. Other children (callee, type args) are + // walked normally so any nested function literals there get plain + // scopes. They don't "contribute to" the assignment target. + // Optional calls (`fn?.(() => {})`) are also CallExpression in @lezer/javascript + // (the optional `?.` is an inner token), so they go through this branch. + for (let c = initNode.firstChild; c; c = c.nextSibling) { + if (c.name === 'ArgList') { + for (let arg = c.firstChild; arg; arg = arg.nextSibling) { + if (arg.name === '(' || arg.name === ')' || arg.name === ',') { + continue; + } + _processInitExpr(arg, text, parentChildren, lhs, true); + } + } else { + _processNode(c, text, parentChildren); + } + } + return; + } + case 'AwaitExpression': { + // `await wrap(() => {})` keeps wrap-pattern inference for the inner call. + // The `await` keyword is itself a child; skip it. + for (let c = initNode.firstChild; c; c = c.nextSibling) { + if (c.name === 'await') { + continue; + } + _processInitExpr(c, text, parentChildren, lhs, throughWrapper); + } + return; + } + case 'TaggedTemplateExpression': { + // `` tag`${() => {}}` `` behaves like `tag(["..."], () => {})`: interpolated + // expressions contribute to the assignment target via the tag call. + for (let c = initNode.firstChild; c; c = c.nextSibling) { + if (c.name !== 'TemplateString') { + // The tag (callee). Walk normally so any nested function literal + // inside it gets a plain scope rather than being inferred. + _processNode(c, text, parentChildren); + continue; + } + for (let part = c.firstChild; part; part = part.nextSibling) { + if (part.name !== 'Interpolation') { + continue; + } + for (let e = part.firstChild; e; e = e.nextSibling) { + if ( + e.name === 'InterpolationStart' || + e.name === 'InterpolationEnd' + ) { + continue; + } + _processInitExpr(e, text, parentChildren, lhs, true); + } + } + } + return; + } + default: + _processNode(initNode, text, parentChildren); + } +} + +// --------------------------------------------------------------------------- +// CST walker +// --------------------------------------------------------------------------- + +function _processNode( + node: SyntaxNode, + text: string, + parentChildren: FunctionScope[] +): void { + switch (node.name) { + case 'MethodDeclaration': { + const keyNode = + node.getChild('PropertyDefinition') ?? + node.getChild('PrivatePropertyDefinition'); + if (!keyNode) { + // Computed key. No stable mapping location. + _walkChildren(node, text, parentChildren); + return; + } + _pushMethodScope(node, text, parentChildren); + return; + } + case 'Property': { + const keyNode = + node.getChild('PropertyDefinition') ?? + node.getChild('PrivatePropertyDefinition'); + + if (!keyNode) { + // Computed key (`{ [expr]() {} }`): no stable mapping location. + _walkChildren(node, text, parentChildren); + return; + } + + const hasColon = node.getChild(':') !== null; + + if (!hasColon) { + const hasParamList = node.getChild('ParamList') !== null; + if (!hasParamList) { + // Shorthand `{ a }`: not a function definition. + return; + } + // Method shorthand: `{ foo() {} }`. + _pushMethodScope(node, text, parentChildren); + return; + } + + // Function-valued property: `{ foo: function() {} }` or `{ foo: () => {} }`. + const funcNode = + node.getChild('FunctionExpression') ?? node.getChild('ArrowFunction'); + if (funcNode) { + _pushMethodScope(node, text, parentChildren); + return; + } + + // Non-function value: walk in case there are nested functions. + _walkChildren(node, text, parentChildren); + return; + } + case 'VariableDeclaration': { + // Lezer flattens multi-declaration: keyword, VarDef, Equals, init, ',', VarDef, Equals, init, ... + // Pair each VariableDefinition with its init expression (the child that + // follows the matching Equals). The init is dispatched to + // _processInitExpr so wrapping calls/new contribute inferred names to + // function arguments. + let pendingId: SyntaxNode | null = null; + let sawEquals = false; + for (let child = node.firstChild; child; child = child.nextSibling) { + if (child.name === 'VariableDefinition') { + pendingId = child; + sawEquals = false; + } else if (child.name === 'Equals') { + sawEquals = true; + } else if (child.name === ',') { + pendingId = null; + sawEquals = false; + } else if (sawEquals && pendingId !== null) { + _processInitExpr( + child, + text, + parentChildren, + { + identifierLoc: pendingId.from, + computedKeyLoc: null, + lhsText: text.slice(pendingId.from, pendingId.to), + }, + /* throughWrapper */ false + ); + pendingId = null; + sawEquals = false; + } else { + // Standalone child (var/let/const keyword, TypeAnnotation, ...): + // walk it for nested functions but don't treat it as an init. + _processNode(child, text, parentChildren); + } + } + return; + } + case 'AssignmentExpression': { + const lhs = _lhsContextFromAssignment(node, text); + // Only infer for plain `=`. Compound assignments (`+=`, `||=`, ...) have + // an UpdateOp instead of an Equals child. Let those fall through to the + // default walk, since the LHS isn't really being assigned a new identity. + if (lhs === null || node.getChild('Equals') === null) { + _walkChildren(node, text, parentChildren); + return; + } + let pastEquals = false; + for (let c = node.firstChild; c; c = c.nextSibling) { + if (c.name === 'Equals') { + pastEquals = true; + continue; + } + if (pastEquals) { + _processInitExpr(c, text, parentChildren, lhs, false); + } else { + // LHS. Walk for nested functions inside computed properties etc. + _processNode(c, text, parentChildren); + } + } + return; + } + case 'FunctionDeclaration': + case 'FunctionExpression': { + const nameNode = node.getChild('VariableDefinition'); + const scope: FunctionScope = { + start: node.from, + end: node.to, + nameMappingLocations: _mappingLocationsForFunction(node, text), + astName: nameNode ? text.slice(nameNode.from, nameNode.to) : null, + kind: 'function', + contributesTo: false, + computedKeyLoc: null, + lhsText: null, + children: [], + }; + parentChildren.push(scope); + _walkChildren(node, text, scope.children); + return; + } + case 'ArrowFunction': { + const scope: FunctionScope = { + start: node.from, + end: node.to, + nameMappingLocations: _mappingLocationsForArrow(node, text), + astName: null, + kind: 'arrow', + contributesTo: false, + computedKeyLoc: null, + lhsText: null, + children: [], + }; + parentChildren.push(scope); + _walkChildren(node, text, scope.children); + return; + } + default: + _walkChildren(node, text, parentChildren); + } +} + +/** + * Create and push a FunctionScope for a MethodDeclaration or a Property with a + * function value. + * + * The scope starts at `node.from` (the method key) so that the function's + * generated position (which Gecko reports at the key) is covered. + * + * For MethodDeclaration (and method-shorthand Property), ParamList/Block are + * direct children of the node. For Property with a function value, funcChild + * is the FunctionExpression/ArrowFunction wrapper. Either way, we walk + * children of funcNode to avoid re-creating a scope for the wrapper itself. + */ +function _pushMethodScope( + node: SyntaxNode, + text: string, + parentChildren: FunctionScope[] +): void { + const funcChild = + node.getChild('FunctionExpression') ?? node.getChild('ArrowFunction'); + const funcNode = funcChild ?? node; + + const funcLocations = + funcNode.name === 'ArrowFunction' + ? _mappingLocationsForArrow(funcNode, text) + : _mappingLocationsForFunction(funcNode, text); + + const scope: FunctionScope = { + start: node.from, + end: node.to, + nameMappingLocations: [..._keyMappingLocation(node), ...funcLocations], + astName: _astNameForKey(node, text), + // Arrow-valued properties (e.g. `{ foo: () => {} }`) must be 'arrow' so + // source-map symbolication skips the funcOffset probe, which resolves to + // the first parameter name rather than the function name. + kind: funcChild?.name === 'ArrowFunction' ? 'arrow' : 'function', + contributesTo: false, + computedKeyLoc: null, + lhsText: null, + children: [], + }; + parentChildren.push(scope); + _walkChildren(funcNode, text, scope.children); +} + +function _walkChildren( + node: SyntaxNode, + text: string, + children: FunctionScope[] +): void { + for (let child = node.firstChild; child; child = child.nextSibling) { + _processNode(child, text, children); + } +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +// @lezer/javascript ships TS and JSX as opt-in dialects. The base parser only +// understands plain JS, so a `.ts` or `.tsx` source feeding back through +// `sourcesContent` produces Error nodes for every type annotation or JSX +// element. Always configure the matching dialect for original sources. +export type JsDialect = '' | 'ts' | 'jsx' | 'ts jsx'; + +const _configuredParsers = new Map(); + +function _parserForDialect(dialect: JsDialect): typeof lezerJsParser { + if (!dialect) { + return lezerJsParser; + } + let parser = _configuredParsers.get(dialect); + if (parser === undefined) { + parser = lezerJsParser.configure({ dialect }); + _configuredParsers.set(dialect, parser); + } + return parser; +} + +/** + * Map a source filename to the Lezer dialect needed to parse it. Looks at the + * file extension only. Accepts URL-style source paths (the typical content of + * a source map's `sources[]`). + */ +export function dialectForFilename(filename: string): JsDialect { + const lower = filename.toLowerCase(); + if (lower.endsWith('.tsx')) { + return 'ts jsx'; + } + if (lower.endsWith('.jsx')) { + return 'jsx'; + } + if ( + lower.endsWith('.ts') || + lower.endsWith('.mts') || + lower.endsWith('.cts') + ) { + return 'ts'; + } + return ''; +} + +/** + * Parse `sourceText` with Lezer and return the top-level function scopes. + * + * `dialect` selects the Lezer dialect: `'ts'` for TypeScript, `'jsx'` for + * JSX, `'ts jsx'` for TSX. Default `''` is plain JS. + * + * Lezer never throws on invalid JS. Error nodes are silently skipped. + */ +export function parseJsScopeTree( + sourceText: string, + dialect: JsDialect = '' +): FunctionScope[] { + const tree = _parserForDialect(dialect).parse(sourceText); + const topLevel: FunctionScope[] = []; + _walkChildren(tree.topNode, sourceText, topLevel); + return topLevel; +} + +/** + * Find the innermost FunctionScope in the tree that contains `offset`. + * + * Returns the path from innermost to outermost as an array: + * result[0]: the innermost scope (the one that directly contains offset) + * result.slice(1): ancestors, nearest first (parent, grandparent, ...) + * + * Returns null if no top-level scope contains the offset. Sibling scopes are + * pushed in source order and are non-overlapping, so a binary search picks the + * candidate at each level in O(log n). + */ +export function findInnermostFunctionScope( + scopes: FunctionScope[], + offset: number +): FunctionScope[] | null { + function search(siblings: FunctionScope[]): FunctionScope[] | null { + const idx = bisectionRightByKey(siblings, offset, (s) => s.start) - 1; + if (idx < 0) { + return null; + } + const candidate = siblings[idx]; + if (offset >= candidate.end) { + return null; + } + const found = search(candidate.children); + if (found !== null) { + found.push(candidate); + return found; + } + return [candidate]; + } + + return search(scopes); +} diff --git a/src/test/unit/source-map-scope-tree.test.ts b/src/test/unit/source-map-scope-tree.test.ts new file mode 100644 index 0000000000..97aae80d3a --- /dev/null +++ b/src/test/unit/source-map-scope-tree.test.ts @@ -0,0 +1,606 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + parseJsScopeTree, + findInnermostFunctionScope, + dialectForFilename, +} from '../../profile-logic/source-map-scope-tree'; + +describe('parseJsScopeTree', () => { + it('returns an empty array for empty source', () => { + const scopes = parseJsScopeTree(''); + expect(scopes).toHaveLength(0); + }); + + it('handles invalid JS without throwing', () => { + expect(() => + parseJsScopeTree('this is not valid javascript !!!') + ).not.toThrow(); + }); + + it('parses a named function declaration', () => { + const src = 'function foo() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.start).toBe(0); + expect(fn.end).toBe(src.length); + expect(fn.astName).toBe('foo'); + expect(fn.kind).toBe('function'); + // nameMappingLocations: [namePos, parenPos] + expect(fn.nameMappingLocations).toHaveLength(2); + expect(src[fn.nameMappingLocations[0]]).toBe('f'); // start of 'foo' + expect(src[fn.nameMappingLocations[1]]).toBe('('); // paren + }); + + it('parses a parenthesised arrow function with two locations', () => { + // ArrowFunction created directly (not through VariableDeclarator). + const src = '[(x) => x]'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.astName).toBeNull(); + // nameMappingLocations: [parenPos, arrowPos ('=')] + expect(fn.nameMappingLocations).toHaveLength(2); + expect(src[fn.nameMappingLocations[0]]).toBe('('); + expect(src[fn.nameMappingLocations[1]]).toBe('='); + expect(src[fn.nameMappingLocations[1] + 1]).toBe('>'); // confirm it's => + }); + + it('parses an anonymous function assigned to a variable', () => { + const src = 'const foo = function() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBeNull(); + expect(fn.kind).toBe('function'); + // nameMappingLocations: [parenPos, identifierPos] + // identifier ('foo') comes last + expect(fn.nameMappingLocations).toHaveLength(2); + expect(src[fn.nameMappingLocations[0]]).toBe('('); + expect(src[fn.nameMappingLocations[1]]).toBe('f'); // start of 'foo' + // identifier loc must be before paren loc (foo comes before function) + expect(fn.nameMappingLocations[1]).toBeLessThan(fn.nameMappingLocations[0]); + }); + + it('uses the arrow pos and identifier pos for arrow assigned to variable', () => { + const src = 'const foo = (x) => x'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.astName).toBeNull(); + // nameMappingLocations: [parenPos, arrowPos, identifierPos('foo')] + expect(fn.nameMappingLocations).toHaveLength(3); + expect(src[fn.nameMappingLocations[0]]).toBe('('); + expect(src[fn.nameMappingLocations[1]]).toBe('='); + expect(src[fn.nameMappingLocations[2]]).toBe('f'); // start of 'foo' + }); + + it('parses a single-param unparenthesised arrow with only the arrow location', () => { + // No `(` before `x` → no paren loc. Only arrow loc. + const src = '[x => x]'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + // nameMappingLocations: [arrowPos] only. No paren + expect(fn.nameMappingLocations).toHaveLength(1); + expect(src[fn.nameMappingLocations[0]]).toBe('='); + expect(src[fn.nameMappingLocations[0] + 1]).toBe('>'); + }); + + it('prepends the key location for a method in an object literal', () => { + const src = 'const o = { foo() {} }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBe('foo'); + expect(fn.kind).toBe('function'); + // nameMappingLocations: [keyPos('foo'), parenPos] + expect(fn.nameMappingLocations.length).toBeGreaterThanOrEqual(1); + expect(src[fn.nameMappingLocations[0]]).toBe('f'); // start of 'foo' + }); + + it('prepends the key location for a named FunctionExpression property', () => { + const src = 'const o = { foo: function() {} }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBe('foo'); + expect(fn.kind).toBe('function'); + expect(src[fn.nameMappingLocations[0]]).toBe('f'); // key 'foo' + expect(src[fn.nameMappingLocations[1]]).toBe('('); // paren + }); + + it('marks an arrow-valued property as kind arrow', () => { + const src = 'const o = { foo: (x) => x }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.astName).toBe('foo'); + expect(src[fn.nameMappingLocations[0]]).toBe('f'); // key + expect(src[fn.nameMappingLocations[1]]).toBe('('); // arrow paren + expect(src[fn.nameMappingLocations[2]]).toBe('='); // arrow => + }); + + it('parses a private class method with # in astName', () => { + const src = 'class C { #foo() {} }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBe('#foo'); + expect(fn.kind).toBe('function'); + expect(src[fn.nameMappingLocations[0]]).toBe('#'); + }); + + it('does not create a scope for a shorthand property', () => { + const src = 'const o = { a, b }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(0); + }); + + it('creates no scope for an empty computed method', () => { + // Computed key `{ [k]() {} }`: no stable key location → we walk the body + // but find no nested functions, so no scopes are created. + const src = 'const k = "foo"; const o = { [k]() {} }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(0); + }); + + it('finds nested functions inside a computed method body', () => { + const src = 'const o = { [k]() { function inner() {} } }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + expect(scopes[0].astName).toBe('inner'); + }); + + it('nests child scopes inside the parent scope', () => { + const src = 'function outer() { function inner() {} }'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const outer = scopes[0]; + expect(outer.astName).toBe('outer'); + expect(outer.children).toHaveLength(1); + + const inner = outer.children[0]; + expect(inner.astName).toBe('inner'); + expect(inner.children).toHaveLength(0); + }); + + it('gives a named function expression its own astName', () => { + // `const foo = function bar() {}` → astName = 'bar', not inferred from foo. + const src = 'const foo = function bar() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBe('bar'); + expect(fn.kind).toBe('function'); + }); + + it('infers name from assignment target for `obj.foo = function() {}`', () => { + const src = 'obj.foo = function() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.astName).toBeNull(); + expect(fn.kind).toBe('function'); + // Last nameMappingLocation should point to 'foo' in obj.foo + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('f'); // 'foo' + }); + + it('does not mark direct assignment as contributesTo', () => { + // `const foo = arrow`. The arrow IS `foo`, so no `<`. + const src = 'const foo = () => {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + expect(scopes[0].contributesTo).toBe(false); + }); + + it('does not mark plain function declarations as contributesTo', () => { + const src = 'function foo() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + expect(scopes[0].contributesTo).toBe(false); + }); + + it('infers name through `new` for `var observer = new C((entries) => {})`', () => { + const src = 'const observer = new IntersectionObserver((entries) => {})'; + const scopes = parseJsScopeTree(src); + // Only one scope: the arrow argument of `new`. No plain anonymous scope. + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.astName).toBeNull(); + expect(fn.contributesTo).toBe(true); + // The last nameMappingLocation should be the start of `observer`. + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('o'); // start of 'observer' + expect(src.slice(lastLoc, lastLoc + 8)).toBe('observer'); + }); + + it('infers name through call for `var x = wrap(() => {})`', () => { + const src = 'const cb = wrap(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('c'); // start of 'cb' + expect(src.slice(lastLoc, lastLoc + 2)).toBe('cb'); + }); + + it('infers names for every function arg of a wrap call', () => { + const src = 'const x = wrap(() => 1, () => 2)'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(2); + + for (const fn of scopes) { + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + const lastLoc = + fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('x'); + } + }); + + it('peels parens around an arrow init: `var x = ((y) => y)`', () => { + // Parens are pure-syntactic: the arrow still IS x, so contributesTo=false. + const src = 'const x = ((y) => y)'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(false); + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('x'); + }); + + it('does not infer for a named FunctionExpression argument', () => { + // `const x = wrap(function bar() {})`. `bar` keeps its own name, so the + // scope must not be marked contributesTo and must carry astName = 'bar'. + const src = 'const x = wrap(function bar() {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('function'); + expect(fn.astName).toBe('bar'); + expect(fn.contributesTo).toBe(false); + }); + + it('does not infer through arbitrary expressions: `var x = obj.prop`', () => { + // A non-wrap expression should not propagate the identifier into nested + // functions. Only direct values, ParenthesizedExpression, + // CallExpression and NewExpression do. + const src = 'const x = obj[(() => 0)()]'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + expect(scopes[0].contributesTo).toBe(false); + }); + + it('infers through `obj.foo = new C(() => {})`', () => { + const src = 'obj.foo = new IntersectionObserver(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('f'); // start of 'foo' + }); + + it('infers through nested wrappers: `var x = wrap1(wrap2(() => {}))`', () => { + const src = 'const x = a(b(() => {}))'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('x'); + }); + + it('does not infer for compound assignments: `x += () => 0`', () => { + const src = 'x += () => 0'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(false); + }); + + it('direct member assignment is not contributesTo: `obj.foo = arrow`', () => { + // `obj.foo = arrow`. The arrow IS obj.foo, so contributesTo=false. + const src = 'obj.foo = () => {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(false); + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('f'); // start of 'foo' + }); + + it('computed-member assignment sets computedKeyLoc: `obj[key] = arrow`', () => { + // `obj[key] = arrow`. The arrow IS obj[key], so contributesTo=false. + // computedKeyLoc points to the start of `key` so the resolver can build + // a compound `${receiver}[${key}]` name. + const src = 'obj[key] = (...args) => f(...args)'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(false); + // Last name mapping location = start of the receiver `obj`. + const lastLoc = fn.nameMappingLocations[fn.nameMappingLocations.length - 1]; + expect(src[lastLoc]).toBe('o'); // start of 'obj' + // computedKeyLoc = start of `key`. + expect(fn.computedKeyLoc).not.toBeNull(); + expect(src[fn.computedKeyLoc!]).toBe('k'); // start of 'key' + expect(src.slice(fn.computedKeyLoc!, fn.computedKeyLoc! + 3)).toBe('key'); + }); + + it('keeps contributesTo through wrapper for `obj[key] = wrap(arrow)`', () => { + // Wrapping with a call still bumps contributesTo to true, while + // computedKeyLoc still tracks the bracket-key. + const src = 'obj[key] = wrap(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.contributesTo).toBe(true); + expect(fn.computedKeyLoc).not.toBeNull(); + }); + + it('preserves astName on named function expressions: `const x = function bar() {}`', () => { + // The function's own name `bar` must win over the inferred variable name. + // The resolver also looks this up in the *original* source so esbuild's + // name-stripped minified output still recovers `bar` at symbolication time. + const src = 'const x = function bar() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('function'); + expect(fn.astName).toBe('bar'); + expect(fn.contributesTo).toBe(false); + }); + + it('does not set computedKeyLoc for non-computed LHS', () => { + expect(parseJsScopeTree('const x = () => {}')[0].computedKeyLoc).toBeNull(); + expect(parseJsScopeTree('obj.foo = () => {}')[0].computedKeyLoc).toBeNull(); + }); + + it('captures lhsText for a simple variable assignment', () => { + const src = 'const foo = () => {}'; + const scopes = parseJsScopeTree(src); + expect(scopes[0].lhsText).toBe('foo'); + }); + + it('captures lhsText for a dotted member assignment', () => { + const src = 'Watcher.prototype.run = function () {}'; + const scopes = parseJsScopeTree(src); + expect(scopes[0].lhsText).toBe('Watcher.prototype.run'); + }); + + it('captures lhsText for a computed member assignment', () => { + const src = 'obj[key] = (...args) => f(...args)'; + const scopes = parseJsScopeTree(src); + expect(scopes[0].lhsText).toBe('obj[key]'); + }); + + it('captures lhsText for a wrap-pattern assignment', () => { + // The wrap wrapper doesn't change the LHS. The lhsText is still the + // assignment target, even though contributesTo is true. + const src = 'const observer = new C(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes[0].lhsText).toBe('observer'); + expect(scopes[0].contributesTo).toBe(true); + }); + + it('captures lhsText for `this.foo = ...`', () => { + const src = 'this.handler = function () {}'; + const scopes = parseJsScopeTree(src); + expect(scopes[0].lhsText).toBe('this.handler'); + }); + + it('does not set lhsText for non-inferred scopes', () => { + // Plain function declarations / arrows without an assignment target + // shouldn't carry lhsText. + expect(parseJsScopeTree('function foo() {}')[0].lhsText).toBeNull(); + expect(parseJsScopeTree('[x => x]')[0].lhsText).toBeNull(); + }); + + it('captures lhsText for `const foo = async function (data) {}`', () => { + // Regression: there was a window where the resolver picked "async" as the + // function name (via a funcOffset probe landing on the `async` keyword). + // The inferred scope must carry the variable name as lhsText so the + // original-source lookup short-circuits the compiled-side probes. + const src = 'const getElementRects = async function (data) {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('function'); + expect(fn.astName).toBeNull(); + expect(fn.lhsText).toBe('getElementRects'); + expect(fn.contributesTo).toBe(false); + }); + + it('captures lhsText for `const foo = async (x) => x`', () => { + const src = 'const getElementRects = async (data) => data'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.astName).toBeNull(); + expect(fn.lhsText).toBe('getElementRects'); + }); + + it('infers name through optional call: `const cb = wrap?.(() => {})`', () => { + // Optional calls share the `CallExpression` node in @lezer/javascript, so + // they should already get wrap-pattern inference. + const src = 'const cb = wrap?.(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + expect(fn.lhsText).toBe('cb'); + }); + + it('infers name through await: `const x = await wrap(() => {})`', () => { + const src = 'const x = await wrap(() => {})'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + expect(fn.lhsText).toBe('x'); + }); + + it('infers name through tagged template: `const x = tag`${() => {}}``', () => { + const src = 'const x = tag`a${() => {}}b`'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(1); + + const fn = scopes[0]; + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + expect(fn.lhsText).toBe('x'); + }); + + it('infers names for every interpolated arrow in a tagged template', () => { + const src = 'const x = tag`a${() => 1}b${() => 2}c`'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(2); + + for (const fn of scopes) { + expect(fn.kind).toBe('arrow'); + expect(fn.contributesTo).toBe(true); + expect(fn.lhsText).toBe('x'); + } + }); + + it('parses a TypeScript function with type annotations using the ts dialect', () => { + // Without the `ts` dialect, the parameter and return type annotations + // produce Error nodes that prevent astName recovery. + const src = 'function foo(x: number, y: string): void { return; }'; + const scopes = parseJsScopeTree(src, 'ts'); + expect(scopes).toHaveLength(1); + expect(scopes[0].astName).toBe('foo'); + expect(scopes[0].kind).toBe('function'); + }); + + it('parses a TSX arrow returning JSX with the `ts jsx` dialect', () => { + const src = + 'const Foo = (props: { name: string }) =>
{props.name}
;'; + const scopes = parseJsScopeTree(src, 'ts jsx'); + expect(scopes).toHaveLength(1); + expect(scopes[0].kind).toBe('arrow'); + expect(scopes[0].lhsText).toBe('Foo'); + }); +}); + +describe('dialectForFilename', () => { + it('returns ts for .ts/.mts/.cts', () => { + expect(dialectForFilename('foo.ts')).toBe('ts'); + expect(dialectForFilename('foo.mts')).toBe('ts'); + expect(dialectForFilename('foo.cts')).toBe('ts'); + }); + + it('returns ts jsx for .tsx', () => { + expect(dialectForFilename('foo.tsx')).toBe('ts jsx'); + }); + + it('returns jsx for .jsx', () => { + expect(dialectForFilename('foo.jsx')).toBe('jsx'); + }); + + it('returns empty string for plain js extensions', () => { + expect(dialectForFilename('foo.js')).toBe(''); + expect(dialectForFilename('foo.mjs')).toBe(''); + expect(dialectForFilename('noext')).toBe(''); + }); + + it('matches case-insensitively', () => { + expect(dialectForFilename('FOO.TSX')).toBe('ts jsx'); + expect(dialectForFilename('Bar.TS')).toBe('ts'); + }); + + it('handles URL-style source paths', () => { + expect(dialectForFilename('webpack:///./src/utils.ts')).toBe('ts'); + expect(dialectForFilename('../src/Component.tsx')).toBe('ts jsx'); + }); +}); + +describe('findInnermostFunctionScope', () => { + it('returns null when no scope contains the offset', () => { + const src = 'const x = 1;'; + const scopes = parseJsScopeTree(src); + expect(findInnermostFunctionScope(scopes, 0)).toBeNull(); + }); + + it('finds the innermost scope for a nested function', () => { + const src = 'function outer() { function inner() {} }'; + const scopes = parseJsScopeTree(src); + + // Offset inside 'inner'. Pick a position inside `function inner() {}` + // 'inner' starts at 28 in the source + const innerOffset = src.indexOf('inner'); + const path = findInnermostFunctionScope(scopes, innerOffset); + expect(path).not.toBeNull(); + expect(path![0].astName).toBe('inner'); + expect(path![1].astName).toBe('outer'); + }); + + it('finds the second of several non-overlapping top-level scopes', () => { + // Exercises the bisection: three siblings, offset inside the second. + const src = 'function a() {} function b() {} function c() {}'; + const scopes = parseJsScopeTree(src); + expect(scopes).toHaveLength(3); + + const bOffset = src.indexOf('function b'); + const path = findInnermostFunctionScope(scopes, bOffset); + expect(path).not.toBeNull(); + expect(path).toHaveLength(1); + expect(path![0].astName).toBe('b'); + }); + + it('returns null for an offset between two sibling scopes', () => { + const src = 'function a() {} function b() {}'; + const scopes = parseJsScopeTree(src); + // Position 15 is the space between the two functions. + expect(findInnermostFunctionScope(scopes, 15)).toBeNull(); + }); +}); diff --git a/src/utils/line-offsets.ts b/src/utils/line-offsets.ts new file mode 100644 index 0000000000..b754efdb3c --- /dev/null +++ b/src/utils/line-offsets.ts @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { bisectionRight } from './bisect'; + +/** + * Build a line-offset table for binary search. + * `lineOffsets[i]` is the character offset of the start of line `i` (0-based). + */ +export function buildLineOffsets(text: string): number[] { + const offsets = [0]; + let pos = 0; + for (;;) { + const next = text.indexOf('\n', pos); + if (next === -1) { + break; + } + offsets.push(next + 1); + pos = next + 1; + } + return offsets; +} + +/** + * Convert a character offset to a 0-based { line, col } position. + * Uses binary search over the line-offset table. + */ +export function offsetToLineCol( + offset: number, + lineOffsets: number[] +): { line: number; col: number } { + const line = bisectionRight(lineOffsets, offset) - 1; + return { line, col: offset - lineOffsets[line] }; +} + +/** + * Convert a 0-based { line, col } position back to a character offset. + */ +export function lineColToOffset( + line: number, + col: number, + lineOffsets: number[] +): number { + return lineOffsets[line] + col; +} diff --git a/yarn.lock b/yarn.lock index aad78a5867..47ab22642e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1801,10 +1801,10 @@ dependencies: vary "^1.1.2" -"@lezer/common@^1.0.0", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.0.tgz#db227b596260189b67ba286387d9dc81fb07c70b" - integrity sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA== +"@lezer/common@^1.0.0", "@lezer/common@^1.2.0", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.2.tgz#d6840db13779e3f1b42e70c9a97c4086d12fae22" + integrity sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ== "@lezer/cpp@^1.0.0": version "1.0.0" @@ -1814,25 +1814,26 @@ "@lezer/highlight" "^1.0.0" "@lezer/lr" "^1.0.0" -"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.2.3": +"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3", "@lezer/highlight@^1.2.3": version "1.2.3" resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.3.tgz#a20f324b71148a2ea9ba6ff42e58bbfaec702857" integrity sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g== dependencies: "@lezer/common" "^1.3.0" -"@lezer/javascript@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.0.0.tgz#60f0b6c295ef526a51fb33603403daad452d9470" - integrity sha512-RawBSrMD9yrVdrXWKn7hqo5BqgBaFelUx80i6p2/V0f+0THjncSSrRC6v3QWVv++RpqWT59L8ujKZjlExJq9xw== +"@lezer/javascript@^1.0.0", "@lezer/javascript@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.5.4.tgz#11746955f957d33c0933f17d7594db54a8b4beea" + integrity sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA== dependencies: - "@lezer/highlight" "^1.0.0" - "@lezer/lr" "^1.0.0" + "@lezer/common" "^1.2.0" + "@lezer/highlight" "^1.1.3" + "@lezer/lr" "^1.3.0" -"@lezer/lr@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.0.0.tgz#89e30c1e710b8715ac5c847ad063418c51d6e750" - integrity sha512-k6DEqBh4HxqO/cVGedb6Ern6LS7K6IOzfydJ5WaqCR26v6UR9sIFyb6PS+5rPUs/mXgnBR/QQCW7RkyjSCMoQA== +"@lezer/lr@^1.0.0", "@lezer/lr@^1.3.0": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.10.tgz#b3acc36e5ad049b74ddb7719594e7e74d9161ff5" + integrity sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A== dependencies: "@lezer/common" "^1.0.0" From 9c42dd30de3b9183325b3cef39cb2f3870a8a8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:26:15 -0400 Subject: [PATCH 06/16] Add JS source map symbolication worker and core logic Implements the off-main-thread part of JS source map symbolication. SourceMapStore wraps the source-map library's WASM-backed SourceMapConsumer, source-map-symbolication.ts walks every func and frame and maps its compiled position back to the original source via exact-match source-map lookups (with scope-tree probes to recover function names), and source-map.worker.ts wires those pieces up as a Web Worker entry point. The action thunk doSourceMapSymbolication spawns the worker on demand and dispatches BULK_SOURCE_MAP_SYMBOLICATION on success. No caller dispatches the thunk yet. --- src/actions/source-map-symbolication.ts | 113 +++ src/profile-logic/source-map-store.ts | 62 ++ src/profile-logic/source-map-symbolication.ts | 946 ++++++++++++++++++ src/profile-logic/source-map-worker-types.ts | 40 + src/profile-logic/source-map.worker.ts | 37 + src/types/actions.ts | 12 + src/types/globals/global.d.ts | 1 + 7 files changed, 1211 insertions(+) create mode 100644 src/actions/source-map-symbolication.ts create mode 100644 src/profile-logic/source-map-store.ts create mode 100644 src/profile-logic/source-map-symbolication.ts create mode 100644 src/profile-logic/source-map-worker-types.ts create mode 100644 src/profile-logic/source-map.worker.ts diff --git a/src/actions/source-map-symbolication.ts b/src/actions/source-map-symbolication.ts new file mode 100644 index 0000000000..6f0688729e --- /dev/null +++ b/src/actions/source-map-symbolication.ts @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { getRawProfileSharedData } from 'firefox-profiler/selectors'; + +import type { + WorkerInput, + WorkerOutput, +} from 'firefox-profiler/profile-logic/source-map-worker-types'; +import type { IndexIntoSourceTable, ThunkAction } from 'firefox-profiler/types'; +import type { RawSourceMap } from 'source-map'; +import { assertExhaustiveCheck } from 'firefox-profiler/utils/types'; + +/** + * Run source map symbolication using previously-fetched source maps from Redux + * state. Offloads source parsing and source-map lookups to a dedicated Web + * Worker so the main thread stays responsive. + * + * Reads the current profile from Redux state at dispatch time. Callers must + * ensure native symbolication has already committed its changes before + * dispatching this action, so that JS symbolication builds on the final state. + * + * `compiledSources` maps bundle IndexIntoSourceTable values to compiled source + * text (fetched alongside the source maps). Used for scope-tree-based function + * name resolution. + */ +export function doSourceMapSymbolication( + resolvedSourceMaps: Map, + compiledSources: Map +): ThunkAction> { + return async (dispatch, getState) => { + if (resolvedSourceMaps.size === 0) { + return; + } + + const shared = getRawProfileSharedData(getState()); + + const input: WorkerInput = { + resolvedSourceMaps, + compiledSources, + funcTable: shared.funcTable, + frameTable: shared.frameTable, + originalLocation: shared.originalLocation, + sources: shared.sources, + stringArray: shared.stringArray, + }; + + const result = await _runSourceMapWorker(input); + switch (result.type) { + case 'success': + dispatch({ + type: 'BULK_SOURCE_MAP_SYMBOLICATION', + newFuncTable: result.newFuncTable, + newFrameTable: result.newFrameTable, + newOriginalLocation: result.newOriginalLocation, + newSources: result.newSources, + newStringArray: result.newStringArray, + }); + break; + case 'error': + console.warn('Source map worker error:', result.message); + break; + case 'no-op': + break; + default: + throw assertExhaustiveCheck(result); + } + }; +} + +/** + * Spawn a one-shot source map worker, send it the input, and return the + * output. The worker is terminated once a response is received or an error + * occurs. Uses the same on-demand spawn pattern as gz.browser.ts. + */ +function _runSourceMapWorker(input: WorkerInput): Promise { + return new Promise((resolve) => { + const reportError = (err: unknown): void => { + resolve({ + type: 'error', + message: err instanceof Error ? err.message : String(err), + }); + }; + + let worker: Worker; + try { + worker = new Worker(SOURCE_MAP_WORKER_PATH); + } catch (err) { + reportError(err); + return; + } + + worker.onmessage = (e: MessageEvent) => { + resolve(e.data); + worker.terminate(); + }; + worker.onerror = (e: ErrorEvent) => { + resolve({ + type: 'error', + message: e.error?.message ?? e.message ?? 'Source map worker failed', + }); + worker.terminate(); + }; + + try { + worker.postMessage(input); + } catch (err) { + reportError(err); + worker.terminate(); + } + }); +} diff --git a/src/profile-logic/source-map-store.ts b/src/profile-logic/source-map-store.ts new file mode 100644 index 0000000000..068719e3d9 --- /dev/null +++ b/src/profile-logic/source-map-store.ts @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { SourceMapConsumer as LibSourceMapConsumer } from 'source-map'; +import type { RawSourceMap } from 'source-map'; +import type { IndexIntoSourceTable } from 'firefox-profiler/types'; + +export type SourceMapConsumer = LibSourceMapConsumer; + +/** + * Holds pre-parsed SourceMapConsumer instances keyed by IndexIntoSourceTable. + * Initialized from the resolvedSourceMaps already fetched at profile load time. + */ +export class SourceMapStore { + private _consumers: Map; + + private constructor(consumers: Map) { + this._consumers = consumers; + } + + static async create( + resolvedSourceMaps: Map, + wasmUrl: string + ): Promise { + const consumers = new Map(); + + // `initialize` is a static method at runtime but typed as instance method + // in the .d.ts. https://github.com/mozilla/source-map/pull/520 + // No-op if already initialized. + await (LibSourceMapConsumer as any).initialize({ + 'lib/mappings.wasm': wasmUrl, + }); + + await Promise.all( + Array.from(resolvedSourceMaps).map(async ([sourceIndex, sourceMap]) => { + try { + const consumer = await new LibSourceMapConsumer(sourceMap); + consumers.set(sourceIndex, consumer); + } catch (e) { + console.warn( + `SourceMapStore: failed to parse source map for sourceIndex=${sourceIndex}:`, + e + ); + } + }) + ); + + return new SourceMapStore(consumers); + } + + getConsumer(sourceIndex: IndexIntoSourceTable): SourceMapConsumer | null { + return this._consumers.get(sourceIndex) ?? null; + } + + destroy(): void { + for (const consumer of this._consumers.values()) { + consumer.destroy(); + } + this._consumers.clear(); + } +} diff --git a/src/profile-logic/source-map-symbolication.ts b/src/profile-logic/source-map-symbolication.ts new file mode 100644 index 0000000000..261179577f --- /dev/null +++ b/src/profile-logic/source-map-symbolication.ts @@ -0,0 +1,946 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * JS source map symbolication. Maps generated (compiled/bundled) JS positions + * back to their original source files, lines, columns, and function names + * using source maps fetched at profile load time. + * + * Storage: the source file lives on funcTable.source (frames only carry + * line/column). The per-row remapping lives in originalLocation. funcTable and + * frameTable index into originalLocation via their `.originalLocation` column. + * + * The shapes the scope tree recognizes (self-named, direct assignment, + * wrap-pattern, computed-member) are catalogued at the top of + * `source-map-scope-tree.ts`. This file is the resolver: it consumes those + * scopes and produces final function names. + * + * ## Function name resolution + * + * Names come from two sources, both optional. The compiled source (the + * bundle text fetched from the browser) enables the full resolution path + * including the Nonymous ancestor chain. The original source is read from + * the source map's `sourcesContent` field, which toolchains may populate + * fully, partially, or not at all. If the compiled source is missing (e.g. + * the profile was captured without the JS Sources feature), we fall back + * to original-source-only resolution without an ancestor chain. + * + * 1. Original source (parsed lazily, cached per file). Look up the innermost + * function scope at the original position. Use `scope.astName` if set, + * else fall back to `scope.lhsText` (the verbatim LHS of an inferred + * scope). This recovers names that minifiers stripped and preserves + * un-minified member chains: + * + * // bundle: const a = function(){}. Original: function mapToPropsProxy(){} + * // -> mapToPropsProxy + * + * Watcher.prototype.run = function() {} // -> Watcher.prototype.run + * this.eventPool_.createObject = ... // -> this.eventPool_.createObject + * obj[key] = fn // -> obj[key] + * + * 2. Compiled source. Walk the scope's `nameMappingLocations` with exact + * source-map probes. On miss, fall back to `scope.astName` (the compiled + * identifier). For computed-member assignments, compose + * `${receiver}[${key}]` from two probes. Used as a fallback when (1) + * didn't yield anything because `sourcesContent` was empty. + * + * ## Ancestor chain + * + * SpiderMonkey's NameFunctions output prefixes a function's name with its + * enclosing chain only when the local name doesn't fully express the + * function's identity on its own. We mirror that: + * + * (Here `() => {}` stands in for any anonymous function or arrow function. + * `wrap(...)` is any call/new expression that the source-map-scope-tree + * treats as a "transparent wrapper". See its docs for the recognized shapes.) + * + * var foo = () => {} // -> foo (bare named, no chain) + * var foo = wrap(() => {}) // -> outer/foo< (contributes-to: chain + `<`) + * obj[key] = () => {} // -> outer/obj[key] (member-style: chain) + * function () { ... } // -> outer/< (anonymous: chain) + * + * When building the chain we keep the immediate parent and any *named* + * ancestors above it. Intermediate anonymous IIFEs are skipped, so bundled + * UMD code reads as `outer/local`, not ` = new Map() +): SourceMapSymbolicationResult | null { + const { funcsToSymbolicate, framesToSymbolicate } = _identifyToSymbolicate( + shared.frameTable, + shared.funcTable, + shared.sources + ); + + if (funcsToSymbolicate.length === 0 && framesToSymbolicate.length === 0) { + return null; + } + + return _applySourceMapSymbolication( + shared, + funcsToSymbolicate, + framesToSymbolicate, + sourceMapStore, + compiledSources + ); +} + +/** + * Walk the frames and funcs to identify which ones need JS symbolication. + * Each JS func with a sourceMapURL is a candidate. Its frames are also + * candidates for line/column remapping. + */ +function _identifyToSymbolicate( + frameTable: FrameTable, + funcTable: FuncTable, + sources: SourceTable +): { + funcsToSymbolicate: IndexIntoFuncTable[]; + framesToSymbolicate: IndexIntoFrameTable[]; +} { + const funcsToSymbolicate: IndexIntoFuncTable[] = []; + const framesToSymbolicate: IndexIntoFrameTable[] = []; + + // Frame tables can have >1M entries, with many frames per func. Cache the + // per-func eligibility so the inner per-frame check is a single array + // lookup instead of three nullability checks against cold columns. + // 0 = unknown, 1 = eligible, 2 = ineligible. + const funcEligibility = new Uint8Array(funcTable.length); + + for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) { + const funcIndex = frameTable.func[frameIndex]; + + let eligibility = funcEligibility[funcIndex]; + if (eligibility === 0) { + eligibility = _isFuncSymbolicable(funcIndex, funcTable, sources) ? 1 : 2; + funcEligibility[funcIndex] = eligibility; + + if (eligibility === 1 && funcTable.originalLocation[funcIndex] === null) { + const funcLine = funcTable.lineNumber[funcIndex]; + const funcCol = funcTable.columnNumber[funcIndex]; + if (funcLine !== null && funcCol !== null) { + funcsToSymbolicate.push(funcIndex); + } + } + } + + if ( + eligibility === 1 && + frameTable.originalLocation[frameIndex] === null && + frameTable.line[frameIndex] !== null && + frameTable.column[frameIndex] !== null + ) { + framesToSymbolicate.push(frameIndex); + } + } + + return { funcsToSymbolicate, framesToSymbolicate }; +} + +function _isFuncSymbolicable( + funcIndex: IndexIntoFuncTable, + funcTable: FuncTable, + sources: SourceTable +): boolean { + if (!funcTable.isJS[funcIndex]) { + return false; + } + const sourceIndex = funcTable.source[funcIndex]; + if (sourceIndex === null) { + return false; + } + return sources.sourceMapURL[sourceIndex] !== null; +} + +/** + * Return true if the source-map has an exact entry at `pos` (0-based col). + * + * `source-map`'s `originalPositionFor` does a greatest-lower-bound (GLB) + * lookup: it returns the largest mapping whose generated position is <= the + * query, so most queries round down to a preceding mapping rather than + * requiring an exact match. We approximate "is there an exact entry at pos?" + * by comparing GLB(pos) with GLB(predecessor of pos): if they differ, `pos` + * must be the start of a new mapping (i.e. an exact entry). + * + * For pos.col > 0 the predecessor is (line, col - 1). For pos.col === 0 the + * predecessor is the end of the previous line: GLB will clip to the last + * actual mapping with (line, col) <= (pos.line - 1, anything), so asking with + * a very large column on the previous line suffices. + * + * TODO: This approximation produces false negatives when two consecutive + * source-map mappings resolve to the same original position (GLB(pos) == + * GLB(predecessor) even though pos is an exact entry). If this causes + * incorrect function names in practice, replace with a direct mapping-entry + * lookup once the source-map library exposes that API. + */ +function _isExactSourceMapEntry( + consumer: SourceMapConsumer, + pos: { line: number; col: number }, + glb: { source: string | null; line: number | null; column: number | null } +): boolean { + let prev; + if (pos.col === 0) { + if (pos.line === 0) { + // First char of the file. No predecessor exists. + return true; + } + // Probe the end of the previous line. + prev = consumer.originalPositionFor({ + line: pos.line, + column: INT32_MAX, + }); + } else { + prev = consumer.originalPositionFor({ + line: pos.line + 1, + column: pos.col - 1, + }); + } + return ( + prev.source !== glb.source || + prev.line !== glb.line || + prev.column !== glb.column + ); +} + +/** + * Apply source map lookups to produce new funcTable, frameTable, and + * originalLocation table. Positions with no mapping in the source map are skipped. + * Returns null if no mappings were actually applied (e.g. no consumers available). + */ +function _applySourceMapSymbolication( + shared: SourceMapSymbolicationInput, + funcsToSymbolicate: IndexIntoFuncTable[], + framesToSymbolicate: IndexIntoFrameTable[], + sourceMapStore: SourceMapStore, + compiledSources: Map +): SourceMapSymbolicationResult | null { + const { frameTable, funcTable, originalLocation, sources, stringArray } = + shared; + + // funcTable, frameTable, and originalLocation are cloned because we overwrite + // values at existing indices (e.g. funcTable.name[i], frameTable.originalLocation[i]). + // + // sources and stringArray are *not* cloned: they're append-only here + // (_findOrCreateSource pushes new entries, StringTable.withBackingArray pushes + // newly interned strings). This function only runs inside the source map worker, + // which received its own structured-cloned copies, so mutating them in place + // is safe for the main thread, and the worker hands the mutated arrays back + // through WorkerOutput. + const newFuncTable = shallowCloneFuncTable(funcTable); + const newFrameTable = shallowCloneFrameTable(frameTable); + const newOriginalLocation = shallowCloneSourceLocationTable(originalLocation); + + const stringTable = StringTable.withBackingArray(stringArray); + + // filename string index to source index, covering all existing null-id + // (original source) entries. Updated in place as new entries are appended. + const sourceByFilename = new Map(); + for (let i = 0; i < sources.length; i++) { + if (sources.id[i] === null) { + sourceByFilename.set(sources.filename[i], i); + } + } + + const compiledSourceCache = new Map(); + + // Parsed scope tree + line offsets of original sources we've seen. Used to + // recover function names stripped during minification (esbuild drops + // named-function-expression identifiers when the inner name isn't referenced + // from inside the function body, so `function mapToPropsProxy()` becomes + // `function()`. Only the original source still knows the name). Map value + // `null` is cached when no content was available so we don't keep retrying. + const originalSourceCache = new Map< + IndexIntoSourceTable, + ParsedSource | null + >(); + + // Function definitions. These get an original source file. + for (const funcIndex of funcsToSymbolicate) { + const sourceIndex = funcTable.source[funcIndex]; + if (sourceIndex === null) { + continue; + } + const consumer = sourceMapStore.getConsumer(sourceIndex); + if (consumer === null) { + continue; + } + const line = funcTable.lineNumber[funcIndex]; + const column = funcTable.columnNumber[funcIndex]; + if (line === null || column === null) { + continue; + } + const remap = _remapPosition( + consumer, + line, + column, + sources, + stringTable, + sourceByFilename, + newOriginalLocation + ); + if (remap === null) { + continue; + } + newFuncTable.originalLocation[funcIndex] = remap.originalLocationIndex; + + // Full resolution (compiled + original, with ancestor chain) when the + // compiled bundle is available. Otherwise (e.g. the profile was captured + // without the JS Sources feature) fall back to original-source-only + // resolution, which still recovers names from `sourcesContent`. + const compiledText = compiledSources.get(sourceIndex); + let resolvedName: string | null; + if (compiledText !== undefined) { + let compiled = compiledSourceCache.get(sourceIndex); + if (compiled === undefined) { + compiled = { + tree: parseJsScopeTree(compiledText), + lineOffsets: buildLineOffsets(compiledText), + }; + compiledSourceCache.set(sourceIndex, compiled); + } + + resolvedName = _resolveFunctionName( + consumer, + remap.original, + line, + column, + compiled.lineOffsets, + compiled.tree, + sources, + remap.originalSourceIndex, + originalSourceCache + ); + } else { + resolvedName = _resolveOriginalName( + sources, + remap.originalSourceIndex, + remap.original, + originalSourceCache + ); + } + if (resolvedName) { + newFuncTable.name[funcIndex] = stringTable.indexForString(resolvedName); + } + } + + // Frame execution points. Remap line/column and capture the original source + // file. In most cases this matches the func's original source, but for + // inlined code it may differ (e.g. a function from utils.ts inlined into + // app.ts maps frames back to utils.ts). + for (const frameIndex of framesToSymbolicate) { + const sourceIndex = funcTable.source[frameTable.func[frameIndex]]; + if (sourceIndex === null) { + continue; + } + const consumer = sourceMapStore.getConsumer(sourceIndex); + if (consumer === null) { + continue; + } + const line = frameTable.line[frameIndex]; + const column = frameTable.column[frameIndex]; + if (line === null || column === null) { + continue; + } + const remap = _remapPosition( + consumer, + line, + column, + sources, + stringTable, + sourceByFilename, + newOriginalLocation + ); + if (remap === null) { + continue; + } + newFrameTable.originalLocation[frameIndex] = remap.originalLocationIndex; + } + + if (newOriginalLocation.length === originalLocation.length) { + return null; + } + + return { newFuncTable, newFrameTable, newOriginalLocation }; +} + +/** + * Remap a single (line, column) through the source map and append a new row + * to newOriginalLocation. Returns the new row index plus the resolved original + * position. Returns null when the source map has no mapping at that position. + * + * Gecko line/column are 1-based. source-map expects 0-based columns and + * returns 0-based columns; we convert in both directions here so callers + * stay in Gecko's convention. + */ +function _remapPosition( + consumer: SourceMapConsumer, + line: number, + column: number, + sources: SourceTable, + stringTable: StringTable, + sourceByFilename: Map, + newOriginalLocation: SourceLocationTable +): { + originalLocationIndex: number; + originalSourceIndex: IndexIntoSourceTable; + original: OriginalPosition; +} | null { + const original = consumer.originalPositionFor({ line, column: column - 1 }); + if (original.source === null || original.line === null) { + return null; + } + const originalSourceIndex = _findOrCreateSource( + sources, + stringTable, + original.source, + sourceByFilename + ); + + // Extract original source content from sourcesContent if not yet seen. + // nullOnMissing=true so we get null instead of a throw when not embedded. + if (sources.content[originalSourceIndex] === null) { + const content = consumer.sourceContentFor(original.source, true); + if (content !== null) { + sources.content[originalSourceIndex] = content; + } + } + + const originalLocationIndex = newOriginalLocation.length; + newOriginalLocation.source.push(originalSourceIndex); + newOriginalLocation.line.push(original.line); + newOriginalLocation.column.push((original.column ?? 0) + 1); + newOriginalLocation.length++; + + return { + originalLocationIndex, + originalSourceIndex, + original: original as OriginalPosition, + }; +} + +/** + * Resolve the original function name. + * + * Resolution order: + * 1. Look the function up in the **original** source (parsed from the + * source map's sourcesContent). If the innermost scope at that position + * has its own `astName` (i.e. it's a named function declaration / named + * function expression / method / property), that name wins. This is what + * recovers names that the minifier stripped (e.g. esbuild drops + * `function mapToPropsProxy()` to `function()` when the inner name isn't + * self-referenced). + * 2. Otherwise probe the compiled-source scope tree at each + * `nameMappingLocation` with an exact source-map lookup, falling back to + * the compiled scope's `astName`. + * + * The Nonymous-style ancestor chain (`outer/.../local`) is only emitted when + * the local segment doesn't fully express the function's identity on its + * own: either the local is anonymous, "contributes to" the target (passed + * through a wrapping call/`new`, gets `<`), or is a computed-member + * expression (`receiver[key]`). A simple named direct assignment + * (`var x = anonFn`) returns just `x`, matching SpiderMonkey's output. + * + * For computed-member assignments (`obj[key] = fn`), `scope.computedKeyLoc` + * triggers a second source-map probe so the resolved name is composed as + * `${receiver}[${key}]`. + * + * Returns null if no name could be determined. + */ +function _resolveFunctionName( + consumer: SourceMapConsumer, + original: OriginalPosition, + line: number, + column: number, + lineOffsets: number[], + scopeTree: FunctionScope[], + sources: SourceTable, + originalSourceIndex: IndexIntoSourceTable, + originalSourceCache: Map +): string | null { + // 1. Original-source name lookup. Prefers the function's own declared + // identifier, falling back to the verbatim LHS text of the surrounding + // assignment. When it produces a simple (non-member) name, we return + // immediately. When it's member-style (`Foo.prototype.bar` / `obj[key]`), + // we keep it but drop into the chain branch below so the result carries + // the enclosing function, matching SpiderMonkey's output shape (e.g. + // `outer/Foo.prototype.bar`). + const originalName = _resolveOriginalName( + sources, + originalSourceIndex, + original, + originalSourceCache + ); + if (originalName !== null && !_isMemberStyleName(originalName)) { + return originalName; + } + + // Gecko uses 1-based line/column. Convert to 0-based for char-offset math. + const funcOffset = lineColToOffset(line - 1, column - 1, lineOffsets); + // scopePath[0] = innermost scope, scopePath[1..] = ancestors (nearest first). + const scopePath = findInnermostFunctionScope(scopeTree, funcOffset); + + if (scopePath === null) { + return originalName; + } + + const scope = scopePath[0]; + const ancestors = scopePath.slice(1); + + // Use the original-source name if available. Otherwise fall back to + // compiled-source resolution. Skipping the compiled path when the original + // source already named the function avoids reapplying computed-member + // composition on top of an `lhsText` of `obj[key]` (would double brackets). + const resolvedName = + originalName ?? + _resolveCompiledName(scope, funcOffset, consumer, original, lineOffsets); + + // Bare named local. Return it without the ancestor chain. Covers + // simple named function declarations (`function foo(){}`), named + // function expressions, and direct variable assignments + // (`var foo = function() {}` / `var foo = () => {}`). Matches SpiderMonkey's + // NameFunctions output, which only prefixes the chain when the local + // segment doesn't fully express the function's identity (anonymous, + // contributes-to, computed-member, or any other member-style name where + // the receiver references an outer-scope identifier). + const needsChain = + resolvedName === null || + scope.contributesTo || + scope.computedKeyLoc !== null || + _isMemberStyleName(resolvedName); + if (!needsChain) { + return resolvedName; + } + + // Build the ancestor chain. Walk innermost-first and keep: + // - the immediate parent (always, even if anonymous: the local being + // directly nested inside an IIFE is useful context). + // - every named ancestor above it (i.e. anything that resolves to a + // real identifier via the source map or AST). + // Skip non-immediate anonymous wrappers. This matches SpiderMonkey's + // output for bundled UMD code where the function we care about lives + // inside a few anonymous IIFEs. Gecko emits e.g. `ewH8/ s.kind === 'named') + ) { + return null; + } + + return serializeNonymousName([...ancestorSegments.reverse(), local]); +} + +/** + * True when the resolved name has property-access structure + * (`obj.foo`, `Foo.prototype.bar`, `obj[key]`, `arr[0]`, ...). The receiver + * in those names references an outer-scope identifier, so the result is + * only fully meaningful with the enclosing scope chain prefixed, + * matching SpiderMonkey's `outer/Foo.prototype.bar` shape. + * + * Plain `foo` / `_foo` / `$foo` stay bare. + */ +function _isMemberStyleName(name: string): boolean { + return name.includes('.') || name.includes('['); +} + +/** + * Probe a single offset in the compiled source: convert to line/col, look it + * up through the source map, and return the mapping's `name` only when the + * mapping is "exact" at that offset. Returns null on any miss. + */ +function _probeExactName( + consumer: SourceMapConsumer, + locOffset: number, + lineOffsets: number[] +): string | null { + const pos = offsetToLineCol(locOffset, lineOffsets); + // source-map's originalPositionFor uses 1-based lines, 0-based columns. + const glb = consumer.originalPositionFor({ + line: pos.line + 1, + column: pos.col, + }); + if ( + glb.source === null || + glb.name === null || + !_isExactSourceMapEntry(consumer, pos, glb) + ) { + return null; + } + return glb.name; +} + +/** + * Compiled-source name resolution: walk the scope's `nameMappingLocations` + * with exact source-map probes, fall back to `scope.astName`, and finally + * compose `${receiver}[${key}]` when the scope came from a computed-member + * assignment. + * + * Probe strategy depends on scope kind: + * + * Regular functions (FunctionDeclaration / FunctionExpression / method): + * Try `funcOffset` first with a relaxed accept rule (the mapping returned by + * the GLB lookup at funcOffset just has to resolve to the same original + * position as the function itself). esbuild often emits a wide mapping there + * so the stricter "exact entry" check (GLB(pos) != GLB(pos - 1), see + * `_isExactSourceMapEntry`) fails even though the mapping IS for this + * function. `nameMappingLocations` then use that stricter exact check. + * + * Arrow functions: + * `funcOffset` sits at the parameter/arrow position, not a function + * identifier. For renamed params (`profile` -> `e`), the GLB lookup there + * returns the parameter name. Skip funcOffset and probe + * `nameMappingLocations` only. + */ +function _resolveCompiledName( + scope: FunctionScope, + funcOffset: number, + consumer: SourceMapConsumer, + original: OriginalPosition, + lineOffsets: number[] +): string | null { + let resolvedName: string | null = null; + + if (scope.kind === 'function') { + const pos = offsetToLineCol(funcOffset, lineOffsets); + const glb = consumer.originalPositionFor({ + line: pos.line + 1, + column: pos.col, + }); + if ( + glb.name !== null && + glb.source === original.source && + glb.line === original.line && + glb.column !== null && + glb.column === original.column + ) { + resolvedName = glb.name; + } + } + + if (resolvedName === null) { + for (const locOffset of scope.nameMappingLocations) { + const name = _probeExactName(consumer, locOffset, lineOffsets); + if (name !== null) { + resolvedName = name; + break; + } + } + } + + // Chrome fallback: if no probe yielded a named entry, use the AST identifier + // (the compiled name). No-op for renamed functions. Resolves unminified + // methods that the source map didn't tag with a name. + if (resolvedName === null) { + resolvedName = scope.astName; + } + + // Computed-member assignment (`obj[key] = fn`): compose `${receiver}[${key}]`. + // Fall back to just the receiver if the key isn't resolvable. + if (resolvedName !== null && scope.computedKeyLoc !== null) { + const keyName = _probeExactName( + consumer, + scope.computedKeyLoc, + lineOffsets + ); + if (keyName !== null) { + resolvedName = `${resolvedName}[${keyName}]`; + } + } + + return resolvedName; +} + +/** + * Look up the function at `original.line` / `original.column` in the original + * source (parsed lazily and cached) and return a name derived from its scope. + * + * Resolution preference, highest to lowest: + * 1. `astName`: the function's own declared identifier (named function + * declaration / named function expression / method / property key). + * Recovers names that minifiers strip, e.g. esbuild dropping + * `function bar()` to `function()` when `bar` isn't self-referenced. + * 2. `lhsText`: the verbatim source text of the assignment-target LHS + * when the function was inferred from one. The original source is + * preserved in the source map's `sourcesContent`, so this carries + * the original (un-minified) member chain: `Watcher.prototype.run`, + * `this.eventPool_.createObject`, `obj[key]`, etc. + * + * Returns null if the original source isn't embedded, the parsed tree has + * no function containing the position, or the innermost scope has neither + * an `astName` nor an `lhsText`. + */ +function _resolveOriginalName( + sources: SourceTable, + originalSourceIndex: IndexIntoSourceTable, + original: OriginalPosition, + cache: Map +): string | null { + // A cached `null` means we saw no content the last time we looked, but a + // later _remapPosition from a different consumer may have populated it + // since. Re-check `sources.content` on every cached miss so the upgrade + // doesn't depend on iteration order across consumers. + let entry = cache.get(originalSourceIndex); + if (entry === undefined || entry === null) { + const sourceContent = sources.content[originalSourceIndex]; + if (sourceContent === null) { + cache.set(originalSourceIndex, null); + return null; + } + entry = { + tree: parseJsScopeTree( + sourceContent, + dialectForFilename(original.source) + ), + lineOffsets: buildLineOffsets(sourceContent), + }; + cache.set(originalSourceIndex, entry); + } + // source-map's original.line is 1-based, column is 0-based. + const offset = lineColToOffset( + original.line - 1, + original.column ?? 0, + entry.lineOffsets + ); + const path = findInnermostFunctionScope(entry.tree, offset); + if (path === null) { + return null; + } + const scope = path[0]; + return scope.astName ?? scope.lhsText; +} + +/** + * Resolve a NonymousSegment for an ancestor scope: probe its mapping + * locations, then fall back to its AST name. Propagates `contributesTo` + * from the scope itself so wrap-pattern ancestors surface with the `<` + * suffix (e.g. the outer `i2<` in `i2 +): IndexIntoSourceTable { + const filenameStrIndex = stringTable.indexForString(filename); + + const existing = sourceByFilename.get(filenameStrIndex); + if (existing !== undefined) { + return existing; + } + + const index = sources.length; + sources.id.push(null); + sources.filename.push(filenameStrIndex); + // Original source files from source maps don't have start line/column. + sources.startLine.push(1); + sources.startColumn.push(1); + sources.sourceMapURL.push(null); + sources.content.push(null); + sources.length++; + + sourceByFilename.set(filenameStrIndex, index); + return index; +} + +/** + * End-to-end source map symbolication runner: build the SourceMapStore, run + * symbolication, and return a WorkerOutput. + * + * Mutates `input.sources` and `input.stringArray` in place + * (`_findOrCreateSource` appends new sources, `StringTable.withBackingArray` + * appends interned strings). Callers must pass copies if the originals must + * stay untouched. The worker is safe because structured clone already copies + * them at the thread boundary. + */ +export async function runSourceMapSymbolicationCore( + input: WorkerInput, + wasmUrl: string +): Promise { + let sourceMapStore: SourceMapStore | null = null; + try { + const { + resolvedSourceMaps, + compiledSources, + funcTable, + frameTable, + originalLocation, + sources, + stringArray, + } = input; + + sourceMapStore = await SourceMapStore.create(resolvedSourceMaps, wasmUrl); + const result = symbolicateWithSourceMaps( + { frameTable, funcTable, originalLocation, sources, stringArray }, + sourceMapStore, + compiledSources + ); + + if (result === null) { + return { type: 'no-op' }; + } + + return { + type: 'success', + newFuncTable: result.newFuncTable, + newFrameTable: result.newFrameTable, + newOriginalLocation: result.newOriginalLocation, + newSources: sources, + newStringArray: stringArray, + }; + } catch (err) { + return { + type: 'error', + message: err instanceof Error ? err.message : String(err), + }; + } finally { + if (sourceMapStore !== null) { + sourceMapStore.destroy(); + } + } +} diff --git a/src/profile-logic/source-map-worker-types.ts b/src/profile-logic/source-map-worker-types.ts new file mode 100644 index 0000000000..f54a94f9e0 --- /dev/null +++ b/src/profile-logic/source-map-worker-types.ts @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import type { + FuncTable, + FrameTable, + IndexIntoSourceTable, + SourceLocationTable, + SourceTable, +} from 'firefox-profiler/types'; +import type { RawSourceMap } from 'source-map'; +import type { SourceMapSymbolicationInput } from './source-map-symbolication'; + +/** + * Data sent from the main thread to the source map symbolication worker. + * All values are structured-cloned (copied) across the thread boundary. + */ +export type WorkerInput = SourceMapSymbolicationInput & { + resolvedSourceMaps: Map; + // Compiled source texts keyed by bundle source index. + compiledSources: Map; +}; + +/** + * Data sent back from the worker to the main thread. + * On success, the new tables replace the profile's shared tables in Redux state. + */ +export type WorkerOutput = + | { + type: 'success'; + newFuncTable: FuncTable; + newFrameTable: FrameTable; + newOriginalLocation: SourceLocationTable; + // Sources and stringArray may have new entries appended by _findOrCreateSource. + newSources: SourceTable; + newStringArray: string[]; + } + | { type: 'no-op' } + | { type: 'error'; message: string }; diff --git a/src/profile-logic/source-map.worker.ts b/src/profile-logic/source-map.worker.ts new file mode 100644 index 0000000000..e051c839f8 --- /dev/null +++ b/src/profile-logic/source-map.worker.ts @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Web Worker entry point for source map symbolication. + * + * Receives a WorkerInput (all profile tables needed by symbolicateWithSourceMaps), + * runs @lezer/javascript parsing + source-map lookups off the main thread, and + * posts back a WorkerOutput with the updated tables. + * + * Must be built as a separate esbuild bundle (see sourceMapWorkerConfig) so + * that npm dependencies are bundled into the worker output file. + */ + +import { runSourceMapSymbolicationCore } from './source-map-symbolication'; +import type { WorkerInput, WorkerOutput } from './source-map-worker-types'; + +// Override the `self` type: in the browser this file runs as a DedicatedWorker, +// but TypeScript's DOM lib types `self` as `Window & typeof globalThis`. +// We only need the onmessage setter and postMessage for the worker protocol. +interface WorkerScope { + onmessage: ((e: MessageEvent) => void) | null; + postMessage: (message: WorkerOutput) => void; + location: Location; +} +const scope = self as unknown as WorkerScope; + +// Resolve mappings.wasm relative to this worker bundle's URL. The wasm is +// copied alongside the worker output by the esbuild copy plugin, so this +// works under both root and subpath deploys. +const mappingsWasmUrl = new URL('mappings.wasm', scope.location.href).href; + +scope.onmessage = async (e: MessageEvent) => { + const output = await runSourceMapSymbolicationCore(e.data, mappingsWasmUrl); + scope.postMessage(output); +}; diff --git a/src/types/actions.ts b/src/types/actions.ts index e49eddc7ec..3847257421 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -14,6 +14,10 @@ import type { IndexIntoLibs, PageList, IndexIntoSourceTable, + FuncTable, + FrameTable, + SourceLocationTable, + SourceTable, } from './profile'; import type { Thread, @@ -442,6 +446,14 @@ type ReceiveProfileAction = | { readonly type: 'UPDATE_PAGES'; readonly newPages: PageList; + } + | { + readonly type: 'BULK_SOURCE_MAP_SYMBOLICATION'; + readonly newFuncTable: FuncTable; + readonly newFrameTable: FrameTable; + readonly newOriginalLocation: SourceLocationTable; + readonly newSources: SourceTable; + readonly newStringArray: string[]; }; type UrlEnhancerAction = diff --git a/src/types/globals/global.d.ts b/src/types/globals/global.d.ts index 969c46442c..676f09189e 100644 --- a/src/types/globals/global.d.ts +++ b/src/types/globals/global.d.ts @@ -4,6 +4,7 @@ // Added by esbuild's define option declare const AVAILABLE_STAGING_LOCALES: string[] | null; +declare const SOURCE_MAP_WORKER_PATH: string; declare module '*.worker.js' { const content: string; From db2a7be5b1fc4da639cdcc106631d1df6e237b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:28:13 -0400 Subject: [PATCH 07/16] Wire source map worker into the esbuild build system The Web Worker added in the previous patch needs its own bundle so the @lezer/javascript and source-map dependencies (plus the source-map WASM mappings) ship to the browser. Still no caller invokes the worker, so this patch only changes how `yarn build` builds the dist directory. --- jest.config.js | 1 + scripts/build-profiler-cli.mjs | 3 ++ scripts/build.mjs | 24 ++++++++++-- scripts/lib/dev-server.mjs | 11 +++++- scripts/lib/esbuild-configs.mjs | 36 ++++++++++++++++++ scripts/run-dev-server.mjs | 6 ++- src/test/fixtures/node-worker.ts | 1 + src/test/fixtures/source-map.worker.stub.js | 15 ++++++++ src/test/setup.ts | 7 ++++ src/test/unit/source-map-worker-mock.test.ts | 40 ++++++++++++++++++++ 10 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 src/test/fixtures/source-map.worker.stub.js create mode 100644 src/test/unit/source-map-worker-mock.test.ts diff --git a/jest.config.js b/jest.config.js index 3b89ea2c4e..573fd8338e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,6 +30,7 @@ const browserEnvConfig = { globals: { AVAILABLE_STAGING_LOCALES: null, + SOURCE_MAP_WORKER_PATH: 'src/test/fixtures/source-map.worker.stub.js', }, snapshotFormat: { diff --git a/scripts/build-profiler-cli.mjs b/scripts/build-profiler-cli.mjs index 4cff0ef1d9..9e90b69c28 100644 --- a/scripts/build-profiler-cli.mjs +++ b/scripts/build-profiler-cli.mjs @@ -23,6 +23,9 @@ const profilerCliConfig = { define: { __BUILD_HASH__: JSON.stringify(BUILD_HASH), __VERSION__: JSON.stringify(version), + // SOURCE_MAP_WORKER_PATH is injected by the browser build. The CLI doesn't + // use source map workers but the shared code references this constant. + SOURCE_MAP_WORKER_PATH: JSON.stringify('/source-map.worker.js'), }, external: [...nodeBaseConfig.external, 'gecko-profiler-demangle'], }; diff --git a/scripts/build.mjs b/scripts/build.mjs index 13334a63cf..01ebca600d 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -3,14 +3,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import esbuild from 'esbuild'; -import { mainBundleConfig } from './lib/esbuild-configs.mjs'; +import { + mainBundleConfig, + sourceMapWorkerConfig, + getSourceMapWorkerPath, +} from './lib/esbuild-configs.mjs'; import { cleanDist, saveMetafile } from './lib/build-utils.mjs'; async function build() { cleanDist(); - const buildResult = await esbuild.build(mainBundleConfig); + + // Build the worker first so we can read its output path from the metafile + // and inject it into the main bundle via SOURCE_MAP_WORKER_PATH. + const workerResult = await esbuild.build(sourceMapWorkerConfig); + + const buildResult = await esbuild.build({ + ...mainBundleConfig, + define: { + ...mainBundleConfig.define, + SOURCE_MAP_WORKER_PATH: JSON.stringify( + getSourceMapWorkerPath(workerResult.metafile) + ), + }, + }); + saveMetafile(buildResult); - console.log('✅ Main browser build completed'); + console.log('✅ Main browser build and source map worker completed'); } build().catch(console.error); diff --git a/scripts/lib/dev-server.mjs b/scripts/lib/dev-server.mjs index 471924d58e..aae460741e 100644 --- a/scripts/lib/dev-server.mjs +++ b/scripts/lib/dev-server.mjs @@ -56,6 +56,7 @@ export async function startDevServer(buildConfig, options = {}) { fallback = 'index.html', onServerStart, cleanDist = true, + extraWatchConfigs = [], } = options; // Clean dist directory first @@ -77,6 +78,12 @@ export async function startDevServer(buildConfig, options = {}) { // Start watching for changes await buildContext.watch(); + // Watch extra configs (no serving needed, just watch for rebuilds) + const extraContexts = await Promise.all( + extraWatchConfigs.map((config) => esbuild.context(config)) + ); + await Promise.all(extraContexts.map((ctx) => ctx.watch())); + // Create HTTP server const server = http.createServer((req, res) => { // Validate Host header @@ -135,7 +142,9 @@ export async function startDevServer(buildConfig, options = {}) { isShuttingDown = true; console.log('\nShutting down...'); - await buildContext.dispose(); + await Promise.all( + [buildContext, ...extraContexts].map((ctx) => ctx.dispose()) + ); server.close(); process.exit(0); }); diff --git a/scripts/lib/esbuild-configs.mjs b/scripts/lib/esbuild-configs.mjs index 42f97c868b..df7a2cf3b6 100644 --- a/scripts/lib/esbuild-configs.mjs +++ b/scripts/lib/esbuild-configs.mjs @@ -81,6 +81,9 @@ export const mainBundleConfig = { : 'undefined', // no need to define NODE_ENV: // esbuild automatically defines NODE_ENV based on the value for "minify" + // In dev, the worker is not hashed so the path is predictable. + // In production, build.mjs overrides this after building the worker first. + SOURCE_MAP_WORKER_PATH: JSON.stringify('/source-map.worker.js'), }, external: ['zlib'], plugins: [ @@ -98,6 +101,10 @@ export const mainBundleConfig = { { from: ['res/img/favicon.png'], to: ['dist/res/img'] }, { from: ['docs-user/**/*'], to: ['dist/docs'] }, { from: ['locales/**/*'], to: ['dist/locales'] }, + { + from: ['node_modules/source-map/lib/mappings.wasm'], + to: ['dist'], + }, ], }), generateHtmlPlugin({ @@ -108,6 +115,35 @@ export const mainBundleConfig = { ], }; +// Source map worker bundle configuration. +// Built as a standalone IIFE so that npm dependencies (lezer, source-map) are +// bundled into a single file that can be loaded as a Web Worker without needing +// ES module support. In production the output filename includes a content hash +// (e.g. source-map-ABCD1234.worker.js). The path is then injected into the main +// bundle via the SOURCE_MAP_WORKER_PATH define. In dev there is no hash since the +// dev server always serves fresh content and the define can't be updated mid-watch. +export const sourceMapWorkerConfig = { + ...baseConfig, + entryPoints: ['src/profile-logic/source-map.worker.ts'], + outdir: 'dist', + format: 'iife', + platform: 'browser', + target: browserslistToEsbuild(), + sourcemap: true, + splitting: false, + entryNames: isProduction ? '[name]-[hash]' : '[name]', + metafile: true, + plugins: [wasmLoader()], +}; + +export function getSourceMapWorkerPath(metafile) { + const [entryPoint] = sourceMapWorkerConfig.entryPoints; + const [outputPath] = Object.entries(metafile.outputs).find( + ([, output]) => output.entryPoint === entryPoint + ); + return '/' + path.basename(outputPath); +} + // Photon styling build configuration const photonTemplateHTML = fs.readFileSync( path.join(projectRoot, 'res', 'photon', 'index.html'), diff --git a/scripts/run-dev-server.mjs b/scripts/run-dev-server.mjs index 0edc44612a..71d87fef02 100644 --- a/scripts/run-dev-server.mjs +++ b/scripts/run-dev-server.mjs @@ -2,7 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import path from 'path'; -import { mainBundleConfig } from './lib/esbuild-configs.mjs'; +import { + mainBundleConfig, + sourceMapWorkerConfig, +} from './lib/esbuild-configs.mjs'; import { startDevServer } from './lib/dev-server.mjs'; import { serveAndOpenProfile } from './lib/profile-server.mjs'; import yargs from 'yargs'; @@ -22,6 +25,7 @@ startDevServer(mainBundleConfig, { host, distDir: 'dist', cleanDist: true, + extraWatchConfigs: [sourceMapWorkerConfig], onServerStart: (profilerUrl) => { const barAscii = '------------------------------------------------------------------------------------------'; diff --git a/src/test/fixtures/node-worker.ts b/src/test/fixtures/node-worker.ts index 94f05ccb08..df9fe21247 100644 --- a/src/test/fixtures/node-worker.ts +++ b/src/test/fixtures/node-worker.ts @@ -25,6 +25,7 @@ function getWorkerScript(file: string): string { CompressionStream, Response, }; + sandbox.self = sandbox; vm.runInNewContext(scriptContent, sandbox, { filename: "${file}" }); diff --git a/src/test/fixtures/source-map.worker.stub.js b/src/test/fixtures/source-map.worker.stub.js new file mode 100644 index 0000000000..75877e977c --- /dev/null +++ b/src/test/fixtures/source-map.worker.stub.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test-only stub for src/profile-logic/source-map.worker.ts. The real worker +// bundles npm dependencies (lezer, source-map) into an IIFE via esbuild, so it +// can't be loaded directly from source by the node-worker fixture. Tests that +// actually exercise the worker's logic should mock the +// `actions/source-map-symbolication` module. This stub is the fallback that +// keeps the Worker spawn from leaking ENOENT errors and hanging tests when a +// dispatch slips through. + +onmessage = () => { + postMessage({ type: 'no-op' }); +}; diff --git a/src/test/setup.ts b/src/test/setup.ts index 29cd6ea6a5..0f69e4eb7a 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -28,6 +28,13 @@ fetchMock.mockGlobal(); // for files ending in .worker.js: The "default export" is the path to the file. jest.mock('../utils/gz.worker.js', () => 'src/utils/gz.worker.js'); +// The source-map worker is normally bundled as an IIFE by esbuild because its +// dependencies (lezer, source-map) can't run as ES modules in a Web Worker +// context. In tests there is no bundle: SOURCE_MAP_WORKER_PATH is defined as +// a global in jest.config.js to point at a stub that immediately responds with +// { type: 'no-op' }. Tests that actually need to exercise the worker logic must +// mock the 'actions/source-map-symbolication' module directly. + // Install a Worker class which is similar to the DOM Worker class. (global as any).Worker = NodeWorker; diff --git a/src/test/unit/source-map-worker-mock.test.ts b/src/test/unit/source-map-worker-mock.test.ts new file mode 100644 index 0000000000..8349526cfd --- /dev/null +++ b/src/test/unit/source-map-worker-mock.test.ts @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Verifies that the source-map worker spawn is mocked at the build/test-infra +// level (via src/test/setup.ts). Without this mock, dispatching +// doSourceMapSymbolication with non-empty maps would attempt to load the +// nonexistent /source-map.worker.js bundle, throw ENOENT inside the worker +// thread, and leave the dispatched Promise hanging until Jest's per-test +// timeout fires. + +import { doSourceMapSymbolication } from '../../actions/source-map-symbolication'; +import { storeWithProfile } from '../fixtures/stores'; +import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile'; +import type { RawSourceMap } from 'source-map'; + +describe('source map worker stub', function () { + it('does not hang when doSourceMapSymbolication is dispatched with a non-empty map', async function () { + const { profile } = getProfileFromTextSamples('A'); + const { dispatch } = storeWithProfile(profile); + const fakeMap: RawSourceMap = { + version: 3, + file: 'a.js', + sources: ['a.ts'], + names: [], + mappings: '', + }; + + // Without the worker mock, this dispatch never resolves. The stub worker + // responds with { type: 'no-op' } so the dispatch finishes cleanly. + await expect( + dispatch( + doSourceMapSymbolication( + new Map([[0, fakeMap]]), + new Map([[0, 'function a(){}\n']]) + ) + ) + ).resolves.toBeUndefined(); + }); +}); From c66ad625e41156ac3f32c418531f8c4378b0f18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:28:37 -0400 Subject: [PATCH 08/16] Fetch source maps and apply JS symbolication after profile load Activates JS source map symbolication end-to-end. After native symbolication settles, the receive-profile flow now fetches source maps and compiled JS for the bundle sources visible in any track, hands them to doSourceMapSymbolication, and lets the worker rewrite funcTable / frameTable / sourceMapInfo / sources / stringArray in-place via BULK_SOURCE_MAP_SYMBOLICATION. After this patch, sourceMapInfo is populated but UI code still reads the compiled positions. The next patch makes it look at sourceMapInfo. --- src/actions/receive-profile.ts | 99 ++++- src/profile-logic/profile-data.ts | 64 +++ src/reducers/profile-view.ts | 25 ++ .../store/source-map-symbolication.test.ts | 386 ++++++++++++++++++ src/test/unit/profile-data.test.ts | 243 +++++++++++ 5 files changed, 816 insertions(+), 1 deletion(-) create mode 100644 src/test/store/source-map-symbolication.test.ts diff --git a/src/actions/receive-profile.ts b/src/actions/receive-profile.ts index 7eb4182cdd..7244ce36ef 100644 --- a/src/actions/receive-profile.ts +++ b/src/actions/receive-profile.ts @@ -76,7 +76,9 @@ import { determineTimelineType, hasUsefulSamples, } from 'firefox-profiler/profile-logic/profile-data'; +import { doSourceMapSymbolication } from './source-map-symbolication'; +import type { RawSourceMap } from 'source-map'; import type { RequestedLib, ImplementationFilter, @@ -89,6 +91,7 @@ import type { TabID, PageList, MixedObject, + IndexIntoSourceTable, } from 'firefox-profiler/types'; import type { SymbolicationStepInfo } from '../profile-logic/symbolication'; @@ -245,7 +248,28 @@ export function finalizeProfileView( } } - await Promise.all([faviconsPromise, symbolicationPromise]); + // Fetch source maps for all JS sources with a sourceMapURL, then apply JS + // symbolication. Fetching runs in parallel with native symbolication, but + // the worker is only dispatched after native symbolication has committed + // its Redux changes, so neither clobbers the other's funcTable updates. + // Requires WebChannel version 7+. + let sourceMapSymbolicationPromise: Promise | null = null; + if (browserConnection !== null && browserConnection.supportsGetSourceMap) { + // Fetch source maps concurrently with native symbolication. Once both + // have completed, apply JS symbolication on top of the final state. + sourceMapSymbolicationPromise = Promise.all([ + doResolveSourceMaps(profile, browserConnection), + symbolicationPromise, + ]).then(([{ resolvedSourceMaps, compiledSources }]) => + dispatch(doSourceMapSymbolication(resolvedSourceMaps, compiledSources)) + ); + } + + await Promise.all([ + faviconsPromise, + symbolicationPromise, + sourceMapSymbolicationPromise, + ]); }; } @@ -811,6 +835,79 @@ export async function doSymbolicateProfile( dispatch(doneSymbolicating()); } +/** + * Resolve JS source maps for every source in the profile that has both a + * sourceMapURL and a UUID. Fetches source maps via the browser WebChannel. + * + * Also fetches the compiled source text which is required by the scope-tree + * name resolution in symbolicateWithSourceMaps. + */ +async function doResolveSourceMaps( + profile: Profile, + browserConnection: BrowserConnection +): Promise<{ + resolvedSourceMaps: Map; + compiledSources: Map; +}> { + const { sources, stringArray } = profile.shared; + + // Collect every source with a sourceMapURL and a UUID. Only UUID-bearing + // sources can be fetched via the browser WebChannel. + const sourceIndexesWithSourceMaps = new Set(); + for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex++) { + if ( + sources.sourceMapURL[sourceIndex] !== null && + sources.sourceMapURL[sourceIndex] !== undefined && + typeof sources.id[sourceIndex] === 'string' + ) { + sourceIndexesWithSourceMaps.add(sourceIndex); + } + } + + if (sourceIndexesWithSourceMaps.size === 0) { + return { resolvedSourceMaps: new Map(), compiledSources: new Map() }; + } + + // Fetch source maps and compiled sources in parallel, ignoring individual failures. + const resolvedSourceMaps: Map = new Map(); + const compiledSources: Map = new Map(); + + await Promise.all( + Array.from(sourceIndexesWithSourceMaps).map(async (sourceIndex) => { + const filename = stringArray[sources.filename[sourceIndex]]; + // sourceId is guaranteed non-null by the filter above. + const sourceId = sources.id[sourceIndex] as string; + + await Promise.all([ + browserConnection + .getSourceMap(sourceId) + .then((result) => { + resolvedSourceMaps.set(sourceIndex, result); + }) + .catch((e) => { + console.warn( + `Failed to fetch source map for "${filename}" (id=${sourceId}):`, + e + ); + }), + browserConnection + .getJSSource(sourceId) + .then((text) => { + compiledSources.set(sourceIndex, text); + }) + .catch((e) => { + console.warn( + `Failed to fetch compiled source for "${filename}" (id=${sourceId}):`, + e + ); + }), + ]); + }) + ); + + return { resolvedSourceMaps, compiledSources }; +} + export async function retrievePageFaviconsFromBrowser( dispatch: Dispatch, pages: PageList, diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 31d1d6482c..1c1b7a9f5e 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -3993,6 +3993,70 @@ export function _gatherSingleThreadStackReferences( } } +/** + * Collect all source table indices referenced by the given threads. + * Walks samples, jsAllocations, and nativeAllocations, following stack prefix + * chains. Includes both compiled sources (funcTable.source) and + * post-symbolication original sources (via originalLocation). + */ +export function collectSourceIndicesFromThreads( + threadIndexes: Iterable, + threads: RawThread[], + shared: RawProfileSharedData +): Set { + const { stackTable, frameTable, funcTable, originalLocation } = shared; + const sourceIndices = new Set(); + const visitedStacks = makeBitSet(stackTable.length); + + const addStackChain = (stackIndex: IndexIntoStackTable | null) => { + let current = stackIndex; + while (current !== null && !checkBit(visitedStacks, current)) { + setBit(visitedStacks, current); + const frameIndex = stackTable.frame[current]; + const funcIndex = frameTable.func[frameIndex]; + + const compiledSource = funcTable.source[funcIndex]; + if (compiledSource !== null) { + sourceIndices.add(compiledSource); + } + + const frameOriginalLocationIdx = frameTable.originalLocation[frameIndex]; + if (frameOriginalLocationIdx !== null) { + sourceIndices.add(originalLocation.source[frameOriginalLocationIdx]); + } + + const funcOriginalLocationIdx = funcTable.originalLocation[funcIndex]; + if (funcOriginalLocationIdx !== null) { + sourceIndices.add(originalLocation.source[funcOriginalLocationIdx]); + } + + current = stackTable.prefix[current]; + } + }; + + for (const threadIndex of threadIndexes) { + if (threadIndex >= threads.length) { + continue; + } + const thread = threads[threadIndex]; + for (const s of thread.samples.stack) { + addStackChain(s); + } + if (thread.jsAllocations !== undefined) { + for (const s of thread.jsAllocations.stack) { + addStackChain(s); + } + } + if (thread.nativeAllocations !== undefined) { + for (const s of thread.nativeAllocations.stack) { + addStackChain(s); + } + } + } + + return sourceIndices; +} + /** * Creates a new thread with modified frame and stack tables for "nudged" return addresses: * All return addresses are moved backwards by one byte, to point into the "call" diff --git a/src/reducers/profile-view.ts b/src/reducers/profile-view.ts index 9a611c1f69..3d6d54017e 100644 --- a/src/reducers/profile-view.ts +++ b/src/reducers/profile-view.ts @@ -58,6 +58,31 @@ const profile: Reducer = (state = null, action) => { threads: symbolicatedThreads, }; } + case 'BULK_SOURCE_MAP_SYMBOLICATION': { + if (state === null) { + throw new Error( + 'Assumed that a profile would be loaded for JS source map symbolication.' + ); + } + const { + newFuncTable, + newFrameTable, + newOriginalLocation, + newSources, + newStringArray, + } = action; + return { + ...state, + shared: { + ...state.shared, + funcTable: newFuncTable, + frameTable: newFrameTable, + originalLocation: newOriginalLocation, + sources: newSources, + stringArray: newStringArray, + }, + }; + } case 'DONE_SYMBOLICATING': { if (state === null) { throw new Error( diff --git a/src/test/store/source-map-symbolication.test.ts b/src/test/store/source-map-symbolication.test.ts new file mode 100644 index 0000000000..f47927cff0 --- /dev/null +++ b/src/test/store/source-map-symbolication.test.ts @@ -0,0 +1,386 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// End-to-end test for the receive-profile -> JS source map symbolication +// pipeline. Drives `loadProfile` with a mocked BrowserConnection that serves +// a real source map and minified bundle, then asserts on: +// - what was fetched (filtering by id / sourceMapURL / WebChannel version), +// - the post-symbolication profile state. +// +// Worker plumbing: the production worker is bundled by esbuild and replaced +// in jest.config.js with a no-op stub. For these tests we override +// global.Worker with an in-process variant that calls +// runSourceMapSymbolicationCore directly, so the real +// doSourceMapSymbolication action runs its full Redux flow and the worker +// internals are exercised. + +import { SourceMapGenerator } from 'source-map'; + +import { runSourceMapSymbolicationCore } from '../../profile-logic/source-map-symbolication'; +import { loadProfile } from '../../actions/receive-profile'; +import { getRawProfileSharedData } from '../../selectors/profile'; +import { blankStore } from '../fixtures/stores'; +import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile'; + +import type { BrowserConnection } from '../../app-logic/browser-connection'; +import type { Profile } from 'firefox-profiler/types'; +import type { + WorkerInput, + WorkerOutput, +} from '../../profile-logic/source-map-worker-types'; +import type { RawSourceMap } from 'source-map'; + +// Original source file. Indentation and blank lines matter: the mappings +// below address specific (line, column) positions. +const ORIGINAL_SOURCE = `function greet(name) { + return "Hello, " + name; +} +`; + +// Minified single-line bundle. `greet` -> `a`, `name` -> `b`. +const BUNDLE_SOURCE = 'function a(b){return"Hello, "+b}'; + +const ORIGINAL_FILENAME = 'hello.js'; + +// Build a source map for BUNDLE_SOURCE referencing ORIGINAL_SOURCE. Includes +// sourcesContent so symbolication can populate sources.content for offline +// source viewing. +function buildSourceMap(bundleFilename: string): RawSourceMap { + const gen = new SourceMapGenerator({ file: bundleFilename }); + gen.setSourceContent(ORIGINAL_FILENAME, ORIGINAL_SOURCE); + // bundle 1:0 ('function') -> original 1:0 + gen.addMapping({ + source: ORIGINAL_FILENAME, + original: { line: 1, column: 0 }, + generated: { line: 1, column: 0 }, + name: 'greet', + }); + // bundle 1:9 ('a' identifier) -> original 1:9 ('greet' identifier) + gen.addMapping({ + source: ORIGINAL_FILENAME, + original: { line: 1, column: 9 }, + generated: { line: 1, column: 9 }, + name: 'greet', + }); + // bundle 1:14 ('return') -> original 2:2 ('return' inside the body) + gen.addMapping({ + source: ORIGINAL_FILENAME, + original: { line: 2, column: 2 }, + generated: { line: 1, column: 14 }, + }); + return JSON.parse(gen.toString()); +} + +type SourceDescriptor = { + filename: string; + id: string | null; + sourceMapURL: string | null; +}; + +// Build a profile with one JS func per source descriptor. Each func is +// positioned at bundle (line 1, col 10) — the start of the identifier `a` in +// BUNDLE_SOURCE — and each frame at (line 1, col 15) — the start of the +// `return` keyword. That way every eligible source produces a successful +// symbolication when paired with buildSourceMap. +function makeProfileWithJsSources(sources: SourceDescriptor[]): Profile { + // One thread per source so each source appears as the funcTable.source of a + // visible thread's stack. + const textSamples = sources.map((s) => `Ajs[file:${s.filename}]`); + const { profile } = getProfileFromTextSamples(...textSamples); + // Skip native symbolication — we only care about JS source map + // symbolication here. + profile.meta.symbolicated = true; + + const { + funcTable, + frameTable, + sources: sourceTable, + stringArray, + } = profile.shared; + + for (const desc of sources) { + const filenameStrIdx = stringArray.indexOf(desc.filename); + const sourceIndex = sourceTable.filename.findIndex( + (f) => f === filenameStrIdx + ); + if (sourceIndex === -1) { + throw new Error(`No source row for ${desc.filename}`); + } + sourceTable.id[sourceIndex] = desc.id; + if (desc.sourceMapURL !== null) { + const urlIdx = stringArray.length; + stringArray.push(desc.sourceMapURL); + sourceTable.sourceMapURL[sourceIndex] = urlIdx; + } else { + sourceTable.sourceMapURL[sourceIndex] = null; + } + } + + // Position every func + frame inside the bundle. funcs and frames are + // co-indexed with sources in the order they were added. + for (let i = 0; i < sources.length; i++) { + funcTable.lineNumber[i] = 1; + funcTable.columnNumber[i] = 10; + frameTable.line[i] = 1; + frameTable.column[i] = 15; + } + + return profile; +} + +type MockBrowserConnection = BrowserConnection & { + getSourceMap: jest.Mock; + getJSSource: jest.Mock; +}; + +// Build a BrowserConnection that serves the given source map and bundle +// fixtures. WebChannel version 7+ enables source-map fetching in +// finalizeProfileView; v6 disables it. +function makeMockBrowserConnection(opts: { + supportsSourceMapFetching: boolean; + sourceMapsById?: Map; + jsSourcesById?: Map; +}): MockBrowserConnection { + const sourceMapsById = opts.sourceMapsById ?? new Map(); + const jsSourcesById = opts.jsSourcesById ?? new Map(); + return { + supportsGetSourceMap: opts.supportsSourceMapFetching, + getSourceMap: jest.fn(async (id: string) => { + const map = sourceMapsById.get(id); + if (!map) { + throw new Error(`No source map fixture for "${id}"`); + } + return map; + }), + getJSSource: jest.fn(async (id: string) => { + const src = jsSourcesById.get(id); + if (src === undefined) { + throw new Error(`No JS source fixture for "${id}"`); + } + return src; + }), + getProfile: jest.fn(), + getExternalMarkers: jest.fn(), + getExternalPowerTracks: jest.fn(), + querySymbolicationApi: jest.fn(), + getSymbolTable: jest.fn(), + getPageFavicons: jest.fn(), + showFunctionInDevtools: jest.fn(), + } as unknown as MockBrowserConnection; +} + +// In-process replacement for global.Worker that runs the source-map worker +// core directly. See file-top comment for context. +class InProcessSourceMapWorker { + onmessage: ((event: { data: WorkerOutput }) => void) | null = null; + onerror: ((event: ErrorEvent) => void) | null = null; + + postMessage(input: WorkerInput): void { + runSourceMapSymbolicationCore(input, 'ignored-in-node').then((output) => { + if (this.onmessage) { + this.onmessage({ data: output }); + } + }); + } + + terminate(): void {} +} + +describe('receive-profile -> JS source map symbolication', function () { + let savedWorker: unknown; + + beforeEach(function () { + savedWorker = (global as any).Worker; + (global as any).Worker = InProcessSourceMapWorker; + }); + + afterEach(function () { + (global as any).Worker = savedWorker; + }); + + describe('fetching and filtering', function () { + it('fetches source maps and bundle sources for every source with a UUID and sourceMapURL', async function () { + const profile = makeProfileWithJsSources([ + { + filename: 'bundle-a.js', + id: 'uuid-a', + sourceMapURL: 'https://example.com/bundle-a.js.map', + }, + { + filename: 'bundle-b.js', + id: 'uuid-b', + sourceMapURL: 'https://example.com/bundle-b.js.map', + }, + ]); + + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: true, + sourceMapsById: new Map([ + ['uuid-a', buildSourceMap('bundle-a.js')], + ['uuid-b', buildSourceMap('bundle-b.js')], + ]), + jsSourcesById: new Map([ + ['uuid-a', BUNDLE_SOURCE], + ['uuid-b', BUNDLE_SOURCE], + ]), + }); + + const { dispatch, getState } = blankStore(); + await dispatch(loadProfile(profile, { browserConnection })); + + expect( + browserConnection.getSourceMap.mock.calls.map((c) => c[0]) + ).toEqual(expect.arrayContaining(['uuid-a', 'uuid-b'])); + expect(browserConnection.getJSSource.mock.calls.map((c) => c[0])).toEqual( + expect.arrayContaining(['uuid-a', 'uuid-b']) + ); + + // Symbolication actually ran: both funcs are now named `greet`. + const { funcTable, stringArray } = getRawProfileSharedData(getState()); + expect(stringArray[funcTable.name[0]]).toBe('greet'); + expect(stringArray[funcTable.name[1]]).toBe('greet'); + }); + + it('does not fetch source maps when the browser lacks source-map support', async function () { + const profile = makeProfileWithJsSources([ + { + filename: 'bundle.js', + id: 'uuid-x', + sourceMapURL: 'https://example.com/bundle.js.map', + }, + ]); + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: false, + }); + + const { dispatch, getState } = blankStore(); + await dispatch(loadProfile(profile, { browserConnection })); + + expect(browserConnection.getSourceMap).not.toHaveBeenCalled(); + expect(browserConnection.getJSSource).not.toHaveBeenCalled(); + + // No symbolication: the bundled name stays as-is. + const { funcTable, stringArray } = getRawProfileSharedData(getState()); + expect(stringArray[funcTable.name[0]]).toBe('Ajs'); + }); + + it('does not fetch sources without an id or without a sourceMapURL', async function () { + // - has-id-no-map: id set but no sourceMapURL → skip + // - has-map-no-id: sourceMapURL set but null id → skip + // - both: should be fetched + const profile = makeProfileWithJsSources([ + { + filename: 'has-id-no-map.js', + id: 'uuid-1', + sourceMapURL: null, + }, + { + filename: 'has-map-no-id.js', + id: null, + sourceMapURL: 'https://example.com/has-map-no-id.js.map', + }, + { + filename: 'both.js', + id: 'uuid-3', + sourceMapURL: 'https://example.com/both.js.map', + }, + ]); + + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: true, + sourceMapsById: new Map([['uuid-3', buildSourceMap('both.js')]]), + jsSourcesById: new Map([['uuid-3', BUNDLE_SOURCE]]), + }); + + const { dispatch, getState } = blankStore(); + await dispatch(loadProfile(profile, { browserConnection })); + + expect( + browserConnection.getSourceMap.mock.calls.map((c) => c[0]) + ).toEqual(['uuid-3']); + + // Only the eligible source got symbolicated. + const { funcTable, stringArray } = getRawProfileSharedData(getState()); + expect(stringArray[funcTable.name[0]]).toBe('Ajs'); + expect(stringArray[funcTable.name[1]]).toBe('Ajs'); + expect(stringArray[funcTable.name[2]]).toBe('greet'); + }); + + it('silently continues when getSourceMap or getJSSource fails', async function () { + const profile = makeProfileWithJsSources([ + { + filename: 'broken.js', + id: 'uuid-broken', + sourceMapURL: 'https://example.com/broken.js.map', + }, + ]); + // No fixtures registered: getSourceMap and getJSSource throw, but the + // catches in doResolveSourceMaps swallow them. + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: true, + }); + + // Silence the expected warnings. + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + const { dispatch, getState } = blankStore(); + await dispatch(loadProfile(profile, { browserConnection })); + + expect(browserConnection.getSourceMap).toHaveBeenCalledWith( + 'uuid-broken' + ); + expect(browserConnection.getJSSource).toHaveBeenCalledWith('uuid-broken'); + + // No symbolication applied — the load completes cleanly with the func + // unchanged. + const { funcTable, stringArray } = getRawProfileSharedData(getState()); + expect(stringArray[funcTable.name[0]]).toBe('Ajs'); + }); + }); + + describe('post-symbolication profile state', function () { + async function loadAndSymbolicate() { + const profile = makeProfileWithJsSources([ + { + filename: 'bundle.js', + id: 'uuid-x', + sourceMapURL: 'https://example.com/bundle.js.map', + }, + ]); + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: true, + sourceMapsById: new Map([['uuid-x', buildSourceMap('bundle.js')]]), + jsSourcesById: new Map([['uuid-x', BUNDLE_SOURCE]]), + }); + const { dispatch, getState } = blankStore(); + await dispatch(loadProfile(profile, { browserConnection })); + return { dispatch, getState }; + } + + it('renames the minified function to its original identifier', async function () { + const { getState } = await loadAndSymbolicate(); + const { funcTable, stringArray } = getRawProfileSharedData(getState()); + // The bundle defined `function a(b)`. After symbolication, the scope + // tree on the original source recovers the pre-minified name. + expect(stringArray[funcTable.name[0]]).toBe('greet'); + }); + + it('remaps the frame execution position to the original source', async function () { + const { getState } = await loadAndSymbolicate(); + const { frameTable, originalLocation, sources, stringArray } = + getRawProfileSharedData(getState()); + + const frameOriginalLocationIdx = frameTable.originalLocation[0]; + expect(frameOriginalLocationIdx).not.toBeNull(); + const originalSourceIdx = + originalLocation.source[frameOriginalLocationIdx!]; + expect(stringArray[sources.filename[originalSourceIdx]]).toBe( + ORIGINAL_FILENAME + ); + // bundle 1:15 ('return') maps to hello.js 2:3 (1-based). + expect(originalLocation.line[frameOriginalLocationIdx!]).toBe(2); + expect(originalLocation.column[frameOriginalLocationIdx!]).toBe(3); + }); + }); +}); diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index 9e074fe2e0..e4c3519847 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -27,6 +27,7 @@ import { computeTimeColumnForRawSamplesTable, getCallNodeFramePerStack, getTotalNativeSymbolTimingsForCallNode, + collectSourceIndicesFromThreads, } from '../../profile-logic/profile-data'; import { createGeckoProfile, @@ -1844,3 +1845,245 @@ describe('getBacktraceItemsForStack', function () { `); }); }); + +describe('collectSourceIndicesFromThreads', function () { + // Helper to find a source index by filename string. + function sourceIndexForFile( + profile: Profile, + fileName: string + ): IndexIntoSourceTable { + const { sources, stringArray } = profile.shared; + for (let i = 0; i < sources.length; i++) { + if (stringArray[sources.filename[i]] === fileName) { + return i; + } + } + throw new Error(`No source for ${fileName}`); + } + + // Helper to append a new entry to originalLocation and return its index. + function addOriginalLocation( + profile: Profile, + source: IndexIntoSourceTable, + line: number, + column: number + ): number { + const { originalLocation } = profile.shared; + const idx = originalLocation.length; + originalLocation.source.push(source); + originalLocation.line.push(line); + originalLocation.column.push(column); + originalLocation.length++; + return idx; + } + + // Helper to add an "original" source that has no compiled-side references. + function addOriginalSource(profile: Profile, fileName: string): number { + const { sources, stringArray } = profile.shared; + const filenameIdx = stringArray.length; + stringArray.push(fileName); + const idx = sources.length; + sources.id.push(null); + sources.filename.push(filenameIdx); + sources.startLine.push(1); + sources.startColumn.push(1); + sources.sourceMapURL.push(null); + sources.content.push(null); + sources.length++; + return idx; + } + + it('collects compiled sources only from the requested threads', function () { + const { profile } = getProfileFromTextSamples( + ` + A[file:a.js] + B[file:b.js] + `, + ` + C[file:c.js] + D[file:d.js] + `, + ` + E[file:e.js] + ` + ); + + const aJs = sourceIndexForFile(profile, 'a.js'); + const bJs = sourceIndexForFile(profile, 'b.js'); + const cJs = sourceIndexForFile(profile, 'c.js'); + const dJs = sourceIndexForFile(profile, 'd.js'); + const eJs = sourceIndexForFile(profile, 'e.js'); + + expect( + collectSourceIndicesFromThreads([0], profile.threads, profile.shared) + ).toEqual(new Set([aJs, bJs])); + expect( + collectSourceIndicesFromThreads([1], profile.threads, profile.shared) + ).toEqual(new Set([cJs, dJs])); + expect( + collectSourceIndicesFromThreads([0, 2], profile.threads, profile.shared) + ).toEqual(new Set([aJs, bJs, eJs])); + }); + + it('walks the full stack chain via the prefix pointer', function () { + // Top-of-stack is "C", but A and B are in the prefix chain. All three + // must be collected even though only the top frame is referenced from + // samples.stack. + const { profile } = getProfileFromTextSamples(` + A[file:a.js] + B[file:b.js] + C[file:c.js] + `); + + const result = collectSourceIndicesFromThreads( + [0], + profile.threads, + profile.shared + ); + + expect(result).toEqual( + new Set([ + sourceIndexForFile(profile, 'a.js'), + sourceIndexForFile(profile, 'b.js'), + sourceIndexForFile(profile, 'c.js'), + ]) + ); + }); + + it('includes both frame originalLocation and func originalLocation entries', function () { + const { profile } = getProfileFromTextSamples(` + A[file:bundle.js] + B[file:bundle.js] + `); + + const bundle = sourceIndexForFile(profile, 'bundle.js'); + const originalA = addOriginalSource(profile, 'original-a.ts'); + const originalB = addOriginalSource(profile, 'original-b.ts'); + const originalBInline = addOriginalSource(profile, 'inlined.ts'); + + // Func A is mapped to original-a.ts. + const funcA = profile.shared.funcTable.name.findIndex( + (n) => profile.shared.stringArray[n] === 'A' + ); + profile.shared.funcTable.originalLocation[funcA] = addOriginalLocation( + profile, + originalA, + 10, + 1 + ); + + // Func B is mapped to original-b.ts, but one of its frames is inlined + // from inlined.ts (frame originalLocation differs from func originalLocation). + const funcB = profile.shared.funcTable.name.findIndex( + (n) => profile.shared.stringArray[n] === 'B' + ); + profile.shared.funcTable.originalLocation[funcB] = addOriginalLocation( + profile, + originalB, + 20, + 1 + ); + const frameB = profile.shared.frameTable.func.findIndex((f) => f === funcB); + profile.shared.frameTable.originalLocation[frameB] = addOriginalLocation( + profile, + originalBInline, + 5, + 2 + ); + + const result = collectSourceIndicesFromThreads( + [0], + profile.threads, + profile.shared + ); + + expect(result).toEqual( + new Set([bundle, originalA, originalB, originalBInline]) + ); + }); + + it('skips threadIndex values that are out of range', function () { + const { profile } = getProfileFromTextSamples(` + A[file:a.js] + `); + const aJs = sourceIndexForFile(profile, 'a.js'); + + expect( + collectSourceIndicesFromThreads( + [0, 5, 99], + profile.threads, + profile.shared + ) + ).toEqual(new Set([aJs])); + }); + + it('includes sources reached via jsAllocations and nativeAllocations stacks', function () { + const { profile } = getProfileFromTextSamples( + // Samples have one source... + ` + A[file:samples.js] + `, + // ...and the other thread has two unrelated stacks we'll re-attach as + // allocation stacks below. + ` + B[file:alloc-js.js] + C[file:alloc-native.js] + ` + ); + + const samplesJs = sourceIndexForFile(profile, 'samples.js'); + const allocJs = sourceIndexForFile(profile, 'alloc-js.js'); + const allocNative = sourceIndexForFile(profile, 'alloc-native.js'); + + // Grab two distinct stack indices from thread 1 to use as the allocation + // sample stacks for thread 0. + const thread1 = profile.threads[1]; + const allocJsStack = thread1.samples.stack[0]; // top frame = B (alloc-js.js) + // For the native one, walk to a stack whose top is C. + const { stackTable, frameTable, funcTable, stringArray } = profile.shared; + const cFunc = funcTable.name.findIndex((n) => stringArray[n] === 'C'); + let allocNativeStack: number | null = null; + for (let i = 0; i < stackTable.length; i++) { + if (funcTable.source[frameTable.func[stackTable.frame[i]]] !== null) { + if (frameTable.func[stackTable.frame[i]] === cFunc) { + allocNativeStack = i; + break; + } + } + } + if (allocNativeStack === null) { + throw new Error('Could not find a stack for func C'); + } + + profile.threads[0].jsAllocations = { + time: [0], + className: [0], + typeName: [0], + coarseType: [0], + weight: [1], + weightType: 'bytes', + inNursery: [false], + stack: [allocJsStack], + length: 1, + } as any; + profile.threads[0].nativeAllocations = { + time: [0], + weight: [1], + weightType: 'bytes', + stack: [allocNativeStack], + length: 1, + } as any; + + // Only collecting from thread 0. But allocations point into thread 1's + // stacks, which live in the shared stack table. + const result = collectSourceIndicesFromThreads( + [0], + profile.threads, + profile.shared + ); + + expect(result.has(samplesJs)).toBe(true); + expect(result.has(allocJs)).toBe(true); + expect(result.has(allocNative)).toBe(true); + }); +}); From 738ea7b69846d233661474efd62002c2b5a94596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 11 May 2026 16:30:01 -0400 Subject: [PATCH 09/16] Use source-mapped positions in UI components and profile logic The sourceMapInfo table is populated after the previous patch's wire-up, but no UI code read it yet. This patch teaches the source view, call tree, tooltip, line timings, and getOriginAnnotationForFunc to prefer source-mapped positions when present and fall back to the compiled position otherwise. Frame-level entries override func-level entries (so inlined code lands in the right original file). Adds the inline-content fallback in selectors/code.tsx: when the profile already carries sources.content (from a shared profile or from JS symbolication), the source view uses it directly instead of hitting the browser. --- src/app-logic/browser-connection.ts | 2 +- src/components/shared/CallNodeContextMenu.tsx | 14 +- src/components/tooltip/CallNode.tsx | 23 +- src/profile-logic/bottom-box.ts | 29 ++- src/profile-logic/call-tree.ts | 5 +- src/profile-logic/line-timings.ts | 36 ++- src/profile-logic/profile-data.ts | 99 ++++++-- src/profile-logic/transforms.ts | 20 +- src/profile-query/function-annotate.ts | 4 +- src/selectors/code.tsx | 17 +- src/selectors/per-thread/index.ts | 11 +- src/selectors/per-thread/stack-sample.ts | 10 +- src/test/components/BottomBox.test.tsx | 4 +- src/test/fixtures/utils.ts | 8 +- .../store/source-map-symbolication.test.ts | 34 ++- src/test/store/transforms.test.ts | 12 +- src/test/unit/line-timings.test.ts | 87 ++++++- src/test/unit/profile-tree.test.ts | 224 ++++++++++++++++++ 18 files changed, 557 insertions(+), 82 deletions(-) diff --git a/src/app-logic/browser-connection.ts b/src/app-logic/browser-connection.ts index 03ce6266d4..1ef37bede9 100644 --- a/src/app-logic/browser-connection.ts +++ b/src/app-logic/browser-connection.ts @@ -16,13 +16,13 @@ import { getJSSourcesViaWebChannelV7, getSourceMapViaWebChannel, } from './web-channel'; +import type { RawSourceMap } from 'source-map'; import type { Milliseconds, FaviconData, MixedObject, SymbolTableAsTuple, } from 'firefox-profiler/types'; -import type { RawSourceMap } from 'source-map'; /** * This file manages the communication between the profiler and the browser. diff --git a/src/components/shared/CallNodeContextMenu.tsx b/src/components/shared/CallNodeContextMenu.tsx index 1a360f01c6..cfb655cf5c 100644 --- a/src/components/shared/CallNodeContextMenu.tsx +++ b/src/components/shared/CallNodeContextMenu.tsx @@ -253,7 +253,14 @@ class CallNodeContextMenuImpl extends React.PureComponent { const { callNodeIndex, - thread: { funcTable, resourceTable, stringTable, sources }, + thread: { + frameTable, + funcTable, + resourceTable, + stringTable, + sources, + originalLocation, + }, callNodeInfo, } = rightClickedCallNodeInfo; @@ -271,10 +278,13 @@ class CallNodeContextMenuImpl extends React.PureComponent { const funcName = stringTable.getString(funcNameIndex); const originAnnotation = getOriginAnnotationForFunc( funcIndex, + null, + frameTable, funcTable, resourceTable, stringTable, - sources + sources, + originalLocation ); return funcName + (originAnnotation ? ` [${originAnnotation}]` : ''); }) diff --git a/src/components/tooltip/CallNode.tsx b/src/components/tooltip/CallNode.tsx index 220fa723f2..e84107d2f0 100644 --- a/src/components/tooltip/CallNode.tsx +++ b/src/components/tooltip/CallNode.tsx @@ -8,7 +8,10 @@ import { getStackType } from 'firefox-profiler/profile-logic/transforms'; import { parseFileNameFromSymbolication } from 'firefox-profiler/utils/special-paths'; import { formatCallNodeNumberWithUnit } from 'firefox-profiler/utils/format-numbers'; import { Icon } from 'firefox-profiler/components/shared/Icon'; -import { getCategoryPairLabel } from 'firefox-profiler/profile-logic/profile-data'; +import { + getCategoryPairLabel, + getOriginalPositionForFrame, +} from 'firefox-profiler/profile-logic/profile-data'; import { countPositiveValues } from 'firefox-profiler/utils'; import type { @@ -379,9 +382,19 @@ export class TooltipCallNode extends React.PureComponent { let fileName = null; - const sourceIndex = thread.funcTable.source[funcIndex]; - const { sources } = thread; + const { + source: sourceIndex, + line: lineNumber, + column: columnNumber, + } = getOriginalPositionForFrame( + null, + funcIndex, + thread.frameTable, + thread.funcTable, + thread.originalLocation + ); + if (sourceIndex !== null) { const fileNameIndex = sources.filename[sourceIndex]; let fileNameURL = thread.stringTable.getString(fileNameIndex); @@ -390,12 +403,8 @@ export class TooltipCallNode extends React.PureComponent { // If it's a path from symbolication, strip it down to just the actual path. fileNameURL = parseFileNameFromSymbolication(fileNameURL).path; - // JS functions have information about where the function starts. - // Add :: to the URL, if known. - const lineNumber = thread.funcTable.lineNumber[funcIndex]; if (lineNumber !== null) { fileNameURL += ':' + lineNumber; - const columnNumber = thread.funcTable.columnNumber[funcIndex]; if (columnNumber !== null) { fileNameURL += ':' + columnNumber; } diff --git a/src/profile-logic/bottom-box.ts b/src/profile-logic/bottom-box.ts index 70ff159167..8de8ada2a9 100644 --- a/src/profile-logic/bottom-box.ts +++ b/src/profile-logic/bottom-box.ts @@ -16,6 +16,7 @@ import { getCallNodeFramePerStack, getNativeSymbolInfo, getNativeSymbolsForCallNode, + getOriginalPositionForFrame, getTotalNativeSymbolTimingsForCallNode, } from './profile-data'; import { mapGetKeyWithMaxValue } from 'firefox-profiler/utils'; @@ -46,7 +47,13 @@ export function getBottomBoxInfoForCallNode( } = thread; const funcIndex = callNodeInfo.funcForNode(callNodeIndex); - const sourceIndex = funcTable.source[funcIndex]; + const { source: sourceIndex, line: funcLine } = getOriginalPositionForFrame( + null, + funcIndex, + frameTable, + funcTable, + thread.originalLocation + ); const resource = funcTable.resource[funcIndex]; const libIndex = resource !== -1 && resourceTable.type[resource] === ResourceType.Library @@ -98,13 +105,16 @@ export function getBottomBoxInfoForCallNode( ); // Compute the hottest line and instruction address, so we can ask the - // source and assembly view to scroll those into view. - const funcLine = funcTable.lineNumber[funcIndex]; + // source and assembly view to scroll those into view. funcLine and the per-sample + // frame lines come from getOriginalPositionForFrame, so the scroll target lines + // up with the (original) source view's line numbering when symbolicated. const lineTimings = getTotalLineTimingsForCallNode( samples, callNodeFramePerStack, frameTable, - funcLine + funcTable, + funcLine, + thread.originalLocation ); const hottestLine = mapGetKeyWithMaxValue(lineTimings); const addressTimings = getTotalAddressTimingsForCallNode( @@ -149,7 +159,13 @@ export function getBottomBoxInfoForStackFrame( const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; - const sourceIndex = funcTable.source[funcIndex]; + const { source: sourceIndex, line: lineNumber } = getOriginalPositionForFrame( + frameIndex, + funcIndex, + frameTable, + funcTable, + thread.originalLocation + ); const resource = funcTable.resource[funcIndex]; const libIndex = resource !== -1 && resourceTable.type[resource] === ResourceType.Library @@ -173,9 +189,6 @@ export function getBottomBoxInfoForStackFrame( const instructionAddress = nativeSymbol !== null ? frameTable.address[frameIndex] : -1; - // Extract line number from the frame - const lineNumber = frameTable.line[frameIndex]; - return { libIndex, sourceIndex, diff --git a/src/profile-logic/call-tree.ts b/src/profile-logic/call-tree.ts index fe026267cd..6022e6a953 100644 --- a/src/profile-logic/call-tree.ts +++ b/src/profile-logic/call-tree.ts @@ -608,10 +608,13 @@ export class CallTree { _getOriginAnnotation(funcIndex: IndexIntoFuncTable): string { return getOriginAnnotationForFunc( funcIndex, + null, + this._thread.frameTable, this._thread.funcTable, this._thread.resourceTable, this._thread.stringTable, - this._thread.sources + this._thread.sources, + this._thread.originalLocation ); } diff --git a/src/profile-logic/line-timings.ts b/src/profile-logic/line-timings.ts index 8001082d84..e8eb3c3f49 100644 --- a/src/profile-logic/line-timings.ts +++ b/src/profile-logic/line-timings.ts @@ -12,8 +12,10 @@ import type { LineNumber, IndexIntoSourceTable, IndexIntoLineSetTable, + SourceLocationTable, } from 'firefox-profiler/types'; import { SetCollectionBuilder } from 'firefox-profiler/utils/set-collection'; +import { getOriginalPositionForFrame } from './profile-data'; /** * For each stack in `stackTable`, and one specific source file, compute the @@ -47,7 +49,8 @@ export function getStackLineInfo( stackTable: StackTable, frameTable: FrameTable, funcTable: FuncTable, - sourceViewSourceIndex: IndexIntoSourceTable + sourceViewSourceIndex: IndexIntoSourceTable, + originalLocation: SourceLocationTable ): StackLineInfo { const builder = new SetCollectionBuilder(); const stackIndexToLineSetIndex = new Int32Array(stackTable.length); @@ -59,14 +62,20 @@ export function getStackLineInfo( const frame = stackTable.frame[stackIndex]; const func = frameTable.func[frame]; - const sourceIndexOfThisStack = funcTable.source[func]; + const { source: sourceIndexOfThisStack, line: selfLineFromMapping } = + getOriginalPositionForFrame( + frame, + func, + frameTable, + funcTable, + originalLocation + ); + const matchesSource = sourceIndexOfThisStack === sourceViewSourceIndex; if (prefixLineSet === -1 && !matchesSource) { stackIndexToLineSetIndex[stackIndex] = -1; } else { - const selfLineOrNull = matchesSource - ? (frameTable.line[frame] ?? funcTable.lineNumber[func]) - : null; + const selfLineOrNull = matchesSource ? selfLineFromMapping : null; stackIndexToLineSetIndex[stackIndex] = builder.extend( prefixLineSet !== -1 ? prefixLineSet : null, @@ -169,7 +178,9 @@ export function getTotalLineTimingsForCallNode( samples: SamplesLikeTable, callNodeFramePerStack: Int32Array, frameTable: FrameTable, - funcLine: LineNumber | null + funcTable: FuncTable, + funcLine: LineNumber | null, + originalLocation: SourceLocationTable ): Map { const totalPerLine = new Map(); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { @@ -179,11 +190,20 @@ export function getTotalLineTimingsForCallNode( } const callNodeFrame = callNodeFramePerStack[stack]; if (callNodeFrame === -1) { - // This sample does not contribute to the call node's total. Ignore. continue; } - const frameLine = frameTable.line[callNodeFrame]; + // Resolve in the same coordinate space as the source view: helper returns + // a source-mapped line when available, otherwise the frame's compiled + // line, otherwise the func's compiled line. + const funcIndex = frameTable.func[callNodeFrame]; + const { line: frameLine } = getOriginalPositionForFrame( + callNodeFrame, + funcIndex, + frameTable, + funcTable, + originalLocation + ); const line = frameLine !== null ? frameLine : funcLine; if (line === null) { continue; diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 1c1b7a9f5e..8c8f963f04 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -3332,6 +3332,70 @@ function _shouldShowBothOriginAndFileName( return true; } +/** + * Resolves the original (source-mapped) position for a frame's execution point, + * with a 3-tier fallback that always produces a self-consistent + * (source, line, column) triple where all three come from the same file. + * + * Tier 1: frame's own source-map entry (when present). Handles inlined code + * where the frame's original file may differ from the func's. + * Tier 2: func's source-map entry (same condition). + * Tier 3: compiled position: funcTable.source[funcIndex] plus line/column + * from the frame (if frameIndex !== null) else from the func declaration. + * + * Pass frameIndex = null for call sites that only have a funcIndex (e.g., + * tooltip / call-node-level lookups, where the call node represents the func + * rather than a specific frame). + */ +export function getOriginalPositionForFrame( + frameIndex: IndexIntoFrameTable | null, + funcIndex: IndexIntoFuncTable, + frameTable: FrameTable, + funcTable: FuncTable, + originalLocation: SourceLocationTable | null +): { + source: IndexIntoSourceTable | null; + line: number | null; + column: number | null; +} { + if (originalLocation !== null && frameIndex !== null) { + const frameOriginalLocationIdx = frameTable.originalLocation[frameIndex]; + if (frameOriginalLocationIdx !== null) { + return { + source: originalLocation.source[frameOriginalLocationIdx], + line: originalLocation.line[frameOriginalLocationIdx], + column: originalLocation.column[frameOriginalLocationIdx], + }; + } + } + + if (originalLocation !== null) { + const funcOriginalLocationIdx = funcTable.originalLocation[funcIndex]; + if (funcOriginalLocationIdx !== null) { + return { + source: originalLocation.source[funcOriginalLocationIdx], + line: originalLocation.line[funcOriginalLocationIdx], + column: originalLocation.column[funcOriginalLocationIdx], + }; + } + } + + if (frameIndex !== null) { + return { + source: funcTable.source[funcIndex], + line: frameTable.line[frameIndex] ?? funcTable.lineNumber[funcIndex], + column: + frameTable.column[frameIndex] ?? funcTable.columnNumber[funcIndex], + }; + } + + return { + source: funcTable.source[funcIndex], + line: funcTable.lineNumber[funcIndex], + column: funcTable.columnNumber[funcIndex], + }; +} + /** * This function returns the source origin for a function. This can be: * - a filename (javascript or object file or c++ source file) @@ -3339,12 +3403,13 @@ function _shouldShowBothOriginAndFileName( */ export function getOriginAnnotationForFunc( funcIndex: IndexIntoFuncTable, + frameIndex: IndexIntoFrameTable | null, + frameTable: FrameTable, funcTable: FuncTable, resourceTable: ResourceTable, stringTable: StringTable, sources: SourceTable, - frameLineNumber: number | null = null, - frameColumnNumber: number | null = null + originalLocation: SourceLocationTable | null = null ): string { let resourceType = null; let origin = null; @@ -3355,7 +3420,18 @@ export function getOriginAnnotationForFunc( origin = stringTable.getString(resourceNameIndex); } - const sourceIndex = funcTable.source[funcIndex]; + const { + source: sourceIndex, + line: lineNumber, + column: columnNumber, + } = getOriginalPositionForFrame( + frameIndex, + funcIndex, + frameTable, + funcTable, + originalLocation + ); + let fileName; if (sourceIndex !== null) { const urlIndex = sources.filename[sourceIndex]; @@ -3369,19 +3445,10 @@ export function getOriginAnnotationForFunc( // strip it down to just the actual path. fileName = parseFileNameFromSymbolication(fileName).path; - if (frameLineNumber !== null) { - fileName += ':' + frameLineNumber; - if (frameColumnNumber !== null) { - fileName += ':' + frameColumnNumber; - } - } else { - const lineNumber = funcTable.lineNumber[funcIndex]; - if (lineNumber !== null) { - fileName += ':' + lineNumber; - const columnNumber = funcTable.columnNumber[funcIndex]; - if (columnNumber !== null) { - fileName += ':' + columnNumber; - } + if (lineNumber !== null) { + fileName += ':' + lineNumber; + if (columnNumber !== null) { + fileName += ':' + columnNumber; } } } diff --git a/src/profile-logic/transforms.ts b/src/profile-logic/transforms.ts index 5bf82499af..954bf4dc94 100644 --- a/src/profile-logic/transforms.ts +++ b/src/profile-logic/transforms.ts @@ -1614,7 +1614,8 @@ export function getBacktraceItemsForStack( implementationFilter: ImplementationFilter, thread: Thread ): BacktraceItem[] { - const { funcTable, stringTable, resourceTable, sources } = thread; + const { funcTable, stringTable, resourceTable, sources, originalLocation } = + thread; const { stackTable, frameTable } = thread; const unfilteredPath = []; @@ -1627,8 +1628,7 @@ export function getBacktraceItemsForStack( unfilteredPath.push({ category: stackTable.category[stackIndex], funcIndex: frameTable.func[frameIndex], - frameLine: frameTable.line[frameIndex], - frameColumn: frameTable.column[frameIndex], + frameIndex, inlineDepth: frameTable.inlineDepth[frameIndex], stackIndex, }); @@ -1639,26 +1639,20 @@ export function getBacktraceItemsForStack( funcMatchesImplementation(thread, funcIndex) ); return path.map( - ({ - category, - funcIndex, - frameLine, - frameColumn, - inlineDepth, - stackIndex, - }) => { + ({ category, funcIndex, frameIndex, inlineDepth, stackIndex }) => { return { funcName: stringTable.getString(funcTable.name[funcIndex]), category, isFrameLabel: funcTable.resource[funcIndex] === -1, origin: getOriginAnnotationForFunc( funcIndex, + frameIndex, + frameTable, funcTable, resourceTable, stringTable, sources, - frameLine, - frameColumn + originalLocation ), inlineDepth, stackIndex, diff --git a/src/profile-query/function-annotate.ts b/src/profile-query/function-annotate.ts index ddb4ee0e90..e50c86a323 100644 --- a/src/profile-query/function-annotate.ts +++ b/src/profile-query/function-annotate.ts @@ -91,6 +91,7 @@ async function fetchSourceAnnotation( frameTable, funcTable: threadFuncTable, samples, + originalLocation, } = thread; const filename = thread.stringTable.getString( thread.sources.filename[sourceIndex] @@ -101,7 +102,8 @@ async function fetchSourceAnnotation( stackTable, frameTable, threadFuncTable, - sourceIndex + sourceIndex, + originalLocation ); const { totalLineHits, selfLineHits } = getLineTimings( stackLineInfo, diff --git a/src/selectors/code.tsx b/src/selectors/code.tsx index adb5a46152..0356388d7d 100644 --- a/src/selectors/code.tsx +++ b/src/selectors/code.tsx @@ -4,10 +4,10 @@ import { createSelector } from 'reselect'; import type { AssemblyCodeStatus, + IndexIntoSourceTable, Lib, SourceCodeStatus, Selector, - IndexIntoSourceTable, } from 'firefox-profiler/types'; import { getSourceViewSourceIndex, @@ -23,8 +23,19 @@ export const getSourceViewCode: Selector = createSelector( getSourceCodeCache, getSourceViewSourceIndex, - (sourceCodeCache, sourceIndex) => - sourceIndex !== null ? sourceCodeCache.get(sourceIndex) : undefined + getProfileOrNull, + (sourceCodeCache, sourceIndex, profile) => { + if (sourceIndex === null) { + return undefined; + } + // Prefer source content stored in the profile (from source map sourcesContent), + // so the source view works offline and when sharing profiles. + const inlineCode = profile?.shared.sources.content[sourceIndex]; + if (inlineCode !== null && inlineCode !== undefined) { + return { type: 'AVAILABLE', code: inlineCode }; + } + return sourceCodeCache.get(sourceIndex); + } ); export const getAssemblyCodeCache: Selector> = ( diff --git a/src/selectors/per-thread/index.ts b/src/selectors/per-thread/index.ts index 6b777e2ef5..36a3b941a9 100644 --- a/src/selectors/per-thread/index.ts +++ b/src/selectors/per-thread/index.ts @@ -225,17 +225,24 @@ export const selectedNodeSelectors: NodeSelectors = (() => { selectedThreadSelectors.getSelectedCallNodePath, selectedThreadSelectors.getFilteredThread, ProfileSelectors.getSourceTable, - (selectedPath, { stringTable, funcTable, resourceTable }, sources) => { + ( + selectedPath, + { stringTable, frameTable, funcTable, resourceTable, originalLocation }, + sources + ) => { if (!selectedPath.length) { return ''; } return ProfileData.getOriginAnnotationForFunc( ProfileData.getLeafFuncIndex(selectedPath), + null, + frameTable, funcTable, resourceTable, stringTable, - sources + sources, + originalLocation ); } ); diff --git a/src/selectors/per-thread/stack-sample.ts b/src/selectors/per-thread/stack-sample.ts index d39239f75b..cbf3dd4430 100644 --- a/src/selectors/per-thread/stack-sample.ts +++ b/src/selectors/per-thread/stack-sample.ts @@ -155,13 +155,19 @@ export function getStackAndSampleSelectorsPerThread( threadSelectors.getFilteredThread, UrlState.getSourceViewSourceIndex, ( - { stackTable, frameTable, funcTable }: Thread, + { stackTable, frameTable, funcTable, originalLocation }: Thread, sourceIndex ): StackLineInfo | null => { if (sourceIndex === null) { return null; } - return getStackLineInfo(stackTable, frameTable, funcTable, sourceIndex); + return getStackLineInfo( + stackTable, + frameTable, + funcTable, + sourceIndex, + originalLocation + ); } ); diff --git a/src/test/components/BottomBox.test.tsx b/src/test/components/BottomBox.test.tsx index 47e7d23e83..e8b9728770 100644 --- a/src/test/components/BottomBox.test.tsx +++ b/src/test/components/BottomBox.test.tsx @@ -135,8 +135,8 @@ describe('BottomBox', () => { const sourceViewContent = await within(sourceViewElement).findByRole('textbox'); - // Because numbers and strings are split in several element, we're matching - // on the string "line" only. + // Verify source content is rendered. Syntax highlighting splits tokens + // across spans, so we match against each cm-line's full textContent. await within(sourceViewContent).findAllByText('line'); expect(sourceViewContent).toMatchSnapshot(); diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index 6aa4991c73..dda9390d0e 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -350,17 +350,15 @@ export function formatStack( ) { const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; - const frameLine = frameTable.line[frameIndex]; - const frameColumn = frameTable.column[frameIndex]; const funcName = stringTable.getString(funcTable.name[funcIndex]); const origin = getOriginAnnotationForFunc( funcIndex, + frameIndex, + frameTable, funcTable, resourceTable, stringTable, - sources, - frameLine, - frameColumn + sources ); lines.push(`${funcName} (${origin})`); } diff --git a/src/test/store/source-map-symbolication.test.ts b/src/test/store/source-map-symbolication.test.ts index f47927cff0..5ff340b70f 100644 --- a/src/test/store/source-map-symbolication.test.ts +++ b/src/test/store/source-map-symbolication.test.ts @@ -6,7 +6,8 @@ // pipeline. Drives `loadProfile` with a mocked BrowserConnection that serves // a real source map and minified bundle, then asserts on: // - what was fetched (filtering by id / sourceMapURL / WebChannel version), -// - the post-symbolication profile state. +// - the post-symbolication profile state, +// - the source view selector reading the original-source content. // // Worker plumbing: the production worker is bundled by esbuild and replaced // in jest.config.js with a no-op stub. For these tests we override @@ -20,6 +21,9 @@ import { SourceMapGenerator } from 'source-map'; import { runSourceMapSymbolicationCore } from '../../profile-logic/source-map-symbolication'; import { loadProfile } from '../../actions/receive-profile'; import { getRawProfileSharedData } from '../../selectors/profile'; +import { getSourceViewCode } from '../../selectors/code'; +import { stateFromLocation } from '../../app-logic/url-handling'; +import { updateUrlState } from '../../actions/app'; import { blankStore } from '../fixtures/stores'; import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile'; @@ -382,5 +386,33 @@ describe('receive-profile -> JS source map symbolication', function () { expect(originalLocation.line[frameOriginalLocationIdx!]).toBe(2); expect(originalLocation.column[frameOriginalLocationIdx!]).toBe(3); }); + + it('returns the original source content via the source view selector', async function () { + const { dispatch, getState } = await loadAndSymbolicate(); + + // The original source was appended to the sources table during + // symbolication. Find its index by filename. + const { sources, stringArray } = getRawProfileSharedData(getState()); + const originalSourceIndex = sources.filename.findIndex( + (idx) => stringArray[idx] === ORIGINAL_FILENAME + ); + expect(originalSourceIndex).toBeGreaterThanOrEqual(0); + + // Point the source view URL state at the original source. + dispatch( + updateUrlState( + stateFromLocation({ + pathname: '/public/fakehash/', + search: `?sourceViewIndex=${originalSourceIndex}`, + hash: '', + }) + ) + ); + + expect(getSourceViewCode(getState())).toEqual({ + type: 'AVAILABLE', + code: ORIGINAL_SOURCE, + }); + }); }); }); diff --git a/src/test/store/transforms.test.ts b/src/test/store/transforms.test.ts index c99a1953de..76e678f978 100644 --- a/src/test/store/transforms.test.ts +++ b/src/test/store/transforms.test.ts @@ -1436,7 +1436,8 @@ describe('"collapse-direct-recursion" transform', function () { stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + filteredThread.originalLocation ); const lineTimings = getLineTimings(stackLineInfo, samples); @@ -1620,7 +1621,8 @@ describe('"collapse-recursion" transform', function () { stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + filteredThread.originalLocation ); const lineTimings = getLineTimings(stackLineInfo, samples); @@ -1733,7 +1735,8 @@ describe('"collapse-recursion" transform', function () { stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + filteredThread.originalLocation ); const lineTimings = getLineTimings(stackLineInfo, samples); @@ -1869,7 +1872,8 @@ describe('"collapse-recursion" transform', function () { stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + filteredThread.originalLocation ); const lineTimings = getLineTimings(stackLineInfo, samples); diff --git a/src/test/unit/line-timings.test.ts b/src/test/unit/line-timings.test.ts index fceb07b357..82de4b5c71 100644 --- a/src/test/unit/line-timings.test.ts +++ b/src/test/unit/line-timings.test.ts @@ -39,7 +39,8 @@ describe('getStackLineInfo', function () { stackTable, frameTable, funcTable, - fileOneSourceIndex + fileOneSourceIndex, + thread.originalLocation ); // Expect the returned stackIndexToLineSetIndex array to have the same length as the stackTable. @@ -52,14 +53,22 @@ describe('getStackLineInfo', function () { describe('getLineTimings for getStackLineInfo', function () { function getTimings(thread: Thread, file: string) { - const { stackTable, frameTable, funcTable, samples, stringTable } = thread; + const { + stackTable, + frameTable, + funcTable, + samples, + stringTable, + originalLocation, + } = thread; const fileStringIndex = stringTable.indexForString(file); const fileSourceIndex = thread.sources.filename.indexOf(fileStringIndex); const stackLineInfo = getStackLineInfo( stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + originalLocation ); return getLineTimings(stackLineInfo, samples); } @@ -140,7 +149,8 @@ describe('getLineTimings for getStackLineInfo', function () { stackTable, frameTable, funcTable, - fileSourceIndex + fileSourceIndex, + thread.originalLocation ); const lineTimings = getLineTimings(stackLineInfo, samples); @@ -149,6 +159,68 @@ describe('getLineTimings for getStackLineInfo', function () { expect(lineTimings.totalLineHits.get(20)).toBe(1); expect(lineTimings.totalLineHits.get(35)).toBe(1); }); + + it("attributes the func's original line when the frame is not source-mapped but the func is", function () { + // Case from the source-map symbolication design: the func definition was + // mapped to an original source, but the frame's execution point was not + // (e.g. it lacked line/column info, so it was never submitted for + // symbolication). Source determination falls back to the func's original + // source, so line attribution must also come from the func's originalLocation + // entry. Otherwise we'd attribute the compiled line value in + // frameTable.line[frame] onto an original-source view. + const { derivedThreads } = getProfileFromTextSamples(` + A[file:compiled.js][line:5] + B[file:compiled.js][line:7] + `); + const [thread] = derivedThreads; + const { stackTable, frameTable, funcTable, samples, stringTable } = thread; + + // Register an additional source representing the original .ts file. + const originalFilenameIndex = stringTable.indexForString('original.ts'); + const originalSourceIndex = thread.sources.length; + thread.sources.id.push(null); + thread.sources.filename.push(originalFilenameIndex); + thread.sources.sourceMapURL.push(null); + thread.sources.content.push(null); + thread.sources.length++; + + // For both funcs, add an originalLocation entry pointing at the original + // source at a known line. Leave frameTable.originalLocation as null on + // every frame. That is the case under test. + const funcAIndex = frameTable.func[stackTable.frame[0]]; + const funcBIndex = frameTable.func[stackTable.frame[stackTable.length - 1]]; + const funcALine = 100; + const funcBLine = 200; + const originalLocation = thread.originalLocation; + originalLocation.source.push(originalSourceIndex, originalSourceIndex); + originalLocation.line.push(funcALine, funcBLine); + originalLocation.column.push(1, 1); + const funcAOriginalLocationIdx = originalLocation.length; + const funcBOriginalLocationIdx = originalLocation.length + 1; + originalLocation.length += 2; + funcTable.originalLocation[funcAIndex] = funcAOriginalLocationIdx; + funcTable.originalLocation[funcBIndex] = funcBOriginalLocationIdx; + + const stackLineInfo = getStackLineInfo( + stackTable, + frameTable, + funcTable, + originalSourceIndex, + originalLocation + ); + const lineTimings = getLineTimings(stackLineInfo, samples); + + // Without the fix, the compiled line numbers (5 and 7) would have been + // attributed to the original-source view. With the fix, the func's + // original lines are used. + expect(lineTimings.selfLineHits.get(funcBLine)).toBe(1); + expect(lineTimings.selfLineHits.size).toBe(1); + expect(lineTimings.totalLineHits.get(funcALine)).toBe(1); + expect(lineTimings.totalLineHits.get(funcBLine)).toBe(1); + expect(lineTimings.totalLineHits.size).toBe(2); + expect(lineTimings.selfLineHits.get(5)).toBe(undefined); + expect(lineTimings.selfLineHits.get(7)).toBe(undefined); + }); }); describe('getTotalLineTimingsForCallNode', function () { @@ -158,7 +230,8 @@ describe('getTotalLineTimingsForCallNode', function () { defaultCategory: IndexIntoCategoryList, isInverted: boolean ): Map { - const { stackTable, frameTable, funcTable, samples } = thread; + const { stackTable, frameTable, funcTable, samples, originalLocation } = + thread; const nonInvertedCallNodeInfo = getCallNodeInfo( stackTable, frameTable, @@ -186,7 +259,9 @@ describe('getTotalLineTimingsForCallNode', function () { samples, callNodeFramePerStack, frameTable, - funcLine + funcTable, + funcLine, + originalLocation ); } diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index f50cd99fe8..5ab98e1b16 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -16,6 +16,7 @@ import { getCallNodeInfo, getInvertedCallNodeInfo, getOriginAnnotationForFunc, + getOriginalPositionForFrame, filterRawThreadSamplesToRange, getSampleIndexToCallNodeIndex, } from '../../profile-logic/profile-data'; @@ -724,6 +725,8 @@ describe('origin annotation', function () { function getOrigin(funcName: string): string { return getOriginAnnotationForFunc( funcNames.indexOf(funcName), + null, + shared.frameTable, shared.funcTable, shared.resourceTable, stringTable, @@ -749,3 +752,224 @@ describe('origin annotation', function () { expect(getOrigin('D')).toEqual('libxul.so'); }); }); + +describe('getOriginAnnotationForFunc with originalLocation', function () { + function setup() { + const { profile, stringTable } = getProfileFromTextSamples(`A`); + const { shared } = profile; + + const bundleIndex = addSourceToTable( + shared.sources, + stringTable.indexForString('http://example.com/bundle.js') + ); + const originalIndex = addSourceToTable( + shared.sources, + stringTable.indexForString('http://example.com/original.ts') + ); + + // Wire the single func to the bundle and give it a compiled position. + shared.funcTable.source[0] = bundleIndex; + shared.funcTable.lineNumber[0] = 1; + shared.funcTable.columnNumber[0] = 100; + + // The text sample produces one frame referencing func 0. Give it a + // compiled position so tier-3 fallback has something meaningful to surface. + shared.frameTable.line[0] = 5; + shared.frameTable.column[0] = 10; + + function addOriginalLocationRow( + source: number, + line: number, + column: number + ): number { + const idx = shared.originalLocation.length; + shared.originalLocation.source.push(source); + shared.originalLocation.line.push(line); + shared.originalLocation.column.push(column); + shared.originalLocation.length++; + return idx; + } + + function callOrigin(frameOriginalLocationIdx: number | null): string { + shared.frameTable.originalLocation[0] = frameOriginalLocationIdx; + return getOriginAnnotationForFunc( + 0, + 0, + shared.frameTable, + shared.funcTable, + shared.resourceTable, + stringTable, + shared.sources, + shared.originalLocation + ); + } + + return { + shared, + originalIndex, + bundleIndex, + addOriginalLocationRow, + callOrigin, + }; + } + + it('uses the frame original position when both source and line are mapped', function () { + const { originalIndex, addOriginalLocationRow, callOrigin } = setup(); + const idx = addOriginalLocationRow(originalIndex, 42, 7); + expect(callOrigin(idx)).toEqual('http://example.com/original.ts:42:7'); + }); + + it('falls back to func mapping when the frame has no source-map entry', function () { + const { shared, originalIndex, addOriginalLocationRow, callOrigin } = + setup(); + shared.funcTable.originalLocation[0] = addOriginalLocationRow( + originalIndex, + 10, + 4 + ); + expect(callOrigin(null)).toEqual('http://example.com/original.ts:10:4'); + }); + + it("uses the frame's compiled position when the frame has no source-map entry", function () { + const { callOrigin } = setup(); + expect(callOrigin(null)).toEqual('http://example.com/bundle.js:5:10'); + }); +}); + +describe('getOriginalPositionForFrame', function () { + function setup() { + const { profile, stringTable } = getProfileFromTextSamples(`A`); + const { shared } = profile; + + const bundleIndex = addSourceToTable( + shared.sources, + stringTable.indexForString('http://example.com/bundle.js') + ); + const originalIndex = addSourceToTable( + shared.sources, + stringTable.indexForString('http://example.com/original.ts') + ); + + shared.funcTable.source[0] = bundleIndex; + shared.funcTable.lineNumber[0] = 1; + shared.funcTable.columnNumber[0] = 100; + shared.frameTable.line[0] = 5; + shared.frameTable.column[0] = 10; + + function addOriginalLocationRow( + source: number, + line: number, + column: number + ): number { + const idx = shared.originalLocation.length; + shared.originalLocation.source.push(source); + shared.originalLocation.line.push(line); + shared.originalLocation.column.push(column); + shared.originalLocation.length++; + return idx; + } + + return { shared, bundleIndex, originalIndex, addOriginalLocationRow }; + } + + it("returns the frame's source-mapped position when present (tier 1)", function () { + const { shared, originalIndex, addOriginalLocationRow } = setup(); + shared.frameTable.originalLocation[0] = addOriginalLocationRow( + originalIndex, + 42, + 7 + ); + expect( + getOriginalPositionForFrame( + 0, + 0, + shared.frameTable, + shared.funcTable, + shared.originalLocation + ) + ).toEqual({ source: originalIndex, line: 42, column: 7 }); + }); + + it("falls back to the func's source-mapped position when the frame has no source-map entry (tier 2)", function () { + const { shared, originalIndex, addOriginalLocationRow } = setup(); + shared.funcTable.originalLocation[0] = addOriginalLocationRow( + originalIndex, + 10, + 4 + ); + expect( + getOriginalPositionForFrame( + 0, + 0, + shared.frameTable, + shared.funcTable, + shared.originalLocation + ) + ).toEqual({ source: originalIndex, line: 10, column: 4 }); + }); + + it("falls back to the frame's compiled position when no source-map info applies (tier 3)", function () { + const { shared, bundleIndex } = setup(); + expect( + getOriginalPositionForFrame( + 0, + 0, + shared.frameTable, + shared.funcTable, + shared.originalLocation + ) + ).toEqual({ source: bundleIndex, line: 5, column: 10 }); + }); + + it("falls back to the func's compiled line/column when the frame's are null", function () { + const { shared, bundleIndex } = setup(); + shared.frameTable.line[0] = null; + shared.frameTable.column[0] = null; + expect( + getOriginalPositionForFrame( + 0, + 0, + shared.frameTable, + shared.funcTable, + shared.originalLocation + ) + ).toEqual({ source: bundleIndex, line: 1, column: 100 }); + }); + + it("uses the func's position when frameIndex is null (tooltip/call-node sites)", function () { + const { shared, originalIndex, addOriginalLocationRow } = setup(); + shared.funcTable.originalLocation[0] = addOriginalLocationRow( + originalIndex, + 10, + 4 + ); + expect( + getOriginalPositionForFrame( + null, + 0, + shared.frameTable, + shared.funcTable, + shared.originalLocation + ) + ).toEqual({ source: originalIndex, line: 10, column: 4 }); + }); + + it('treats originalLocation = null as no symbolication available', function () { + const { shared, bundleIndex, originalIndex, addOriginalLocationRow } = + setup(); + shared.funcTable.originalLocation[0] = addOriginalLocationRow( + originalIndex, + 10, + 4 + ); + expect( + getOriginalPositionForFrame( + 0, + 0, + shared.frameTable, + shared.funcTable, + null + ) + ).toEqual({ source: bundleIndex, line: 5, column: 10 }); + }); +}); From c580175e4b898d1c79e34ddb517dff7a69f39c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Fri, 22 May 2026 00:40:06 +0200 Subject: [PATCH 10/16] Show progress overlay during JS source map symbolication The source map pipeline (fetching + worker) previously ran invisibly after profile load. This surfaces two generic status messages in the existing SymbolicationStatusOverlay: "Fetching source maps..." while doResolveSourceMaps runs, and "Symbolicating JS source maps..." while the worker runs. Native symbolication takes priority. The source map messages only appear when the native overlay is not active. It's tracked via a new sourceMapSymbolicationStatus on viewOptions. Note that it looks like this component was never localized. We should do it! But I think this is a task for a follow-up --- src/actions/receive-profile.ts | 74 ++++++++++--------- src/actions/source-map-symbolication.ts | 3 + .../app/SymbolicationStatusOverlay.tsx | 32 +++++++- src/reducers/profile-view.ts | 23 ++++++ src/selectors/profile.ts | 4 + .../store/source-map-symbolication.test.ts | 55 +++++++++++++- src/types/actions.ts | 4 + src/types/state.ts | 5 ++ 8 files changed, 161 insertions(+), 39 deletions(-) diff --git a/src/actions/receive-profile.ts b/src/actions/receive-profile.ts index 7244ce36ef..77c5b1028e 100644 --- a/src/actions/receive-profile.ts +++ b/src/actions/receive-profile.ts @@ -258,7 +258,7 @@ export function finalizeProfileView( // Fetch source maps concurrently with native symbolication. Once both // have completed, apply JS symbolication on top of the final state. sourceMapSymbolicationPromise = Promise.all([ - doResolveSourceMaps(profile, browserConnection), + doResolveSourceMaps(profile, browserConnection, dispatch), symbolicationPromise, ]).then(([{ resolvedSourceMaps, compiledSources }]) => dispatch(doSourceMapSymbolication(resolvedSourceMaps, compiledSources)) @@ -844,7 +844,8 @@ export async function doSymbolicateProfile( */ async function doResolveSourceMaps( profile: Profile, - browserConnection: BrowserConnection + browserConnection: BrowserConnection, + dispatch: Dispatch ): Promise<{ resolvedSourceMaps: Map; compiledSources: Map; @@ -872,38 +873,43 @@ async function doResolveSourceMaps( const resolvedSourceMaps: Map = new Map(); const compiledSources: Map = new Map(); - await Promise.all( - Array.from(sourceIndexesWithSourceMaps).map(async (sourceIndex) => { - const filename = stringArray[sources.filename[sourceIndex]]; - // sourceId is guaranteed non-null by the filter above. - const sourceId = sources.id[sourceIndex] as string; - - await Promise.all([ - browserConnection - .getSourceMap(sourceId) - .then((result) => { - resolvedSourceMaps.set(sourceIndex, result); - }) - .catch((e) => { - console.warn( - `Failed to fetch source map for "${filename}" (id=${sourceId}):`, - e - ); - }), - browserConnection - .getJSSource(sourceId) - .then((text) => { - compiledSources.set(sourceIndex, text); - }) - .catch((e) => { - console.warn( - `Failed to fetch compiled source for "${filename}" (id=${sourceId}):`, - e - ); - }), - ]); - }) - ); + dispatch({ type: 'START_SOURCE_MAP_FETCHING' }); + try { + await Promise.all( + Array.from(sourceIndexesWithSourceMaps).map(async (sourceIndex) => { + const filename = stringArray[sources.filename[sourceIndex]]; + // sourceId is guaranteed non-null by the filter above. + const sourceId = sources.id[sourceIndex] as string; + + await Promise.all([ + browserConnection + .getSourceMap(sourceId) + .then((result) => { + resolvedSourceMaps.set(sourceIndex, result); + }) + .catch((e) => { + console.warn( + `Failed to fetch source map for "${filename}" (id=${sourceId}):`, + e + ); + }), + browserConnection + .getJSSource(sourceId) + .then((text) => { + compiledSources.set(sourceIndex, text); + }) + .catch((e) => { + console.warn( + `Failed to fetch compiled source for "${filename}" (id=${sourceId}):`, + e + ); + }), + ]); + }) + ); + } finally { + dispatch({ type: 'DONE_SOURCE_MAP_FETCHING' }); + } return { resolvedSourceMaps, compiledSources }; } diff --git a/src/actions/source-map-symbolication.ts b/src/actions/source-map-symbolication.ts index 6f0688729e..f8ba000b86 100644 --- a/src/actions/source-map-symbolication.ts +++ b/src/actions/source-map-symbolication.ts @@ -46,6 +46,7 @@ export function doSourceMapSymbolication( stringArray: shared.stringArray, }; + dispatch({ type: 'START_SOURCE_MAP_SYMBOLICATION' }); const result = await _runSourceMapWorker(input); switch (result.type) { case 'success': @@ -60,8 +61,10 @@ export function doSourceMapSymbolication( break; case 'error': console.warn('Source map worker error:', result.message); + dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' }); break; case 'no-op': + dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' }); break; default: throw assertExhaustiveCheck(result); diff --git a/src/components/app/SymbolicationStatusOverlay.tsx b/src/components/app/SymbolicationStatusOverlay.tsx index a4157a2e3e..ab96aba373 100644 --- a/src/components/app/SymbolicationStatusOverlay.tsx +++ b/src/components/app/SymbolicationStatusOverlay.tsx @@ -6,10 +6,15 @@ import { PureComponent } from 'react'; import { getProfileViewOptions, getSymbolicationStatus, + getSourceMapSymbolicationStatus, } from 'firefox-profiler/selectors/profile'; import explicitConnect from 'firefox-profiler/utils/connect'; -import type { RequestedLib, State } from 'firefox-profiler/types'; +import type { + RequestedLib, + SourceMapSymbolicationStatus, + State, +} from 'firefox-profiler/types'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './SymbolicationStatusOverlay.css'; @@ -33,6 +38,7 @@ function englishListJoin(list: string[]) { type StateProps = { readonly symbolicationStatus: string; + readonly sourceMapSymbolicationStatus: SourceMapSymbolicationStatus; readonly waitingForLibs: Set; }; @@ -40,7 +46,11 @@ type Props = ConnectedProps<{}, StateProps, {}>; class SymbolicationStatusOverlayImpl extends PureComponent { override render() { - const { symbolicationStatus, waitingForLibs } = this.props; + const { + symbolicationStatus, + sourceMapSymbolicationStatus, + waitingForLibs, + } = this.props; if (symbolicationStatus === 'SYMBOLICATING') { if (waitingForLibs.size > 0) { const libNames = Array.from(waitingForLibs.values()).map( @@ -62,6 +72,23 @@ class SymbolicationStatusOverlayImpl extends PureComponent { ); } + // Native takes priority: only show source-map status when native isn't active. + if (sourceMapSymbolicationStatus === 'FETCHING') { + return ( +
+ + Fetching source maps... +
+ ); + } + if (sourceMapSymbolicationStatus === 'SYMBOLICATING') { + return ( +
+ + Symbolicating JS source maps... +
+ ); + } return
; } } @@ -69,6 +96,7 @@ class SymbolicationStatusOverlayImpl extends PureComponent { export const SymbolicationStatusOverlay = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ symbolicationStatus: getSymbolicationStatus(state), + sourceMapSymbolicationStatus: getSourceMapSymbolicationStatus(state), waitingForLibs: getProfileViewOptions(state).waitingForLibs, }), component: SymbolicationStatusOverlayImpl, diff --git a/src/reducers/profile-view.ts b/src/reducers/profile-view.ts index 3d6d54017e..92fa3f6f69 100644 --- a/src/reducers/profile-view.ts +++ b/src/reducers/profile-view.ts @@ -20,6 +20,7 @@ import type { Reducer, ProfileViewState, SymbolicationStatus, + SourceMapSymbolicationStatus, ThreadViewOptions, ThreadViewOptionsPerThreads, TableViewOptionsPerTab, @@ -161,6 +162,27 @@ const symbolicationStatus: Reducer = ( } }; +const sourceMapSymbolicationStatus: Reducer = ( + state = 'INACTIVE', + action +) => { + switch (action.type) { + case 'START_SOURCE_MAP_FETCHING': + return 'FETCHING'; + case 'START_SOURCE_MAP_SYMBOLICATION': + return 'SYMBOLICATING'; + // Fetching done but worker not yet started. Go back to INACTIVE. + // The next START_SOURCE_MAP_SYMBOLICATION will set it to SYMBOLICATING. + case 'DONE_SOURCE_MAP_FETCHING': + return state === 'FETCHING' ? 'INACTIVE' : state; + case 'BULK_SOURCE_MAP_SYMBOLICATION': + case 'SOURCE_MAP_SYMBOLICATION_FAILED': + return 'INACTIVE'; + default: + return state; + } +}; + export const defaultThreadViewOptions: ThreadViewOptions = { selectedNonInvertedCallNodePath: [], selectedInvertedCallNodePath: [], @@ -851,6 +873,7 @@ const profileViewReducer: Reducer = wrapReducerInResetter( viewOptions: combineReducers({ perThread: viewOptionsPerThread, symbolicationStatus, + sourceMapSymbolicationStatus, waitingForLibs, previewSelection, scrollToSelectionGeneration, diff --git a/src/selectors/profile.ts b/src/selectors/profile.ts index ad7f8ff41c..f5d13060f5 100644 --- a/src/selectors/profile.ts +++ b/src/selectors/profile.ts @@ -70,6 +70,7 @@ import type { State, ProfileViewState, SymbolicationStatus, + SourceMapSymbolicationStatus, MarkerSchema, MarkerSchemaByName, SampleUnits, @@ -105,6 +106,9 @@ export const getProfileRootRange: Selector = (state) => getProfileViewOptions(state).rootRange; export const getSymbolicationStatus: Selector = (state) => getProfileViewOptions(state).symbolicationStatus; +export const getSourceMapSymbolicationStatus: Selector< + SourceMapSymbolicationStatus +> = (state) => getProfileViewOptions(state).sourceMapSymbolicationStatus; export const getScrollToSelectionGeneration: Selector = (state) => getProfileViewOptions(state).scrollToSelectionGeneration; export const getFocusCallTreeGeneration: Selector = (state) => diff --git a/src/test/store/source-map-symbolication.test.ts b/src/test/store/source-map-symbolication.test.ts index 5ff340b70f..4b9cf3d0fe 100644 --- a/src/test/store/source-map-symbolication.test.ts +++ b/src/test/store/source-map-symbolication.test.ts @@ -6,6 +6,7 @@ // pipeline. Drives `loadProfile` with a mocked BrowserConnection that serves // a real source map and minified bundle, then asserts on: // - what was fetched (filtering by id / sourceMapURL / WebChannel version), +// - the status reducer transitions, // - the post-symbolication profile state, // - the source view selector reading the original-source content. // @@ -13,14 +14,18 @@ // in jest.config.js with a no-op stub. For these tests we override // global.Worker with an in-process variant that calls // runSourceMapSymbolicationCore directly, so the real -// doSourceMapSymbolication action runs its full Redux flow and the worker -// internals are exercised. +// doSourceMapSymbolication action runs its full Redux flow +// (START_SOURCE_MAP_SYMBOLICATION -> BULK_SOURCE_MAP_SYMBOLICATION / +// SOURCE_MAP_SYMBOLICATION_FAILED) and the worker internals are exercised. import { SourceMapGenerator } from 'source-map'; import { runSourceMapSymbolicationCore } from '../../profile-logic/source-map-symbolication'; import { loadProfile } from '../../actions/receive-profile'; -import { getRawProfileSharedData } from '../../selectors/profile'; +import { + getSourceMapSymbolicationStatus, + getRawProfileSharedData, +} from '../../selectors/profile'; import { getSourceViewCode } from '../../selectors/code'; import { stateFromLocation } from '../../app-logic/url-handling'; import { updateUrlState } from '../../actions/app'; @@ -340,6 +345,50 @@ describe('receive-profile -> JS source map symbolication', function () { // unchanged. const { funcTable, stringArray } = getRawProfileSharedData(getState()); expect(stringArray[funcTable.name[0]]).toBe('Ajs'); + expect(getSourceMapSymbolicationStatus(getState())).toBe('INACTIVE'); + }); + }); + + describe('status transitions', function () { + it('moves through FETCHING and SYMBOLICATING before settling back to INACTIVE', async function () { + const profile = makeProfileWithJsSources([ + { + filename: 'bundle.js', + id: 'uuid-x', + sourceMapURL: 'https://example.com/bundle.js.map', + }, + ]); + const browserConnection = makeMockBrowserConnection({ + supportsSourceMapFetching: true, + sourceMapsById: new Map([['uuid-x', buildSourceMap('bundle.js')]]), + jsSourcesById: new Map([['uuid-x', BUNDLE_SOURCE]]), + }); + + const store = blankStore(); + // Record every distinct sourceMapSymbolicationStatus value the store + // passes through. + const statuses: string[] = [ + getSourceMapSymbolicationStatus(store.getState()), + ]; + store.subscribe(() => { + const current = getSourceMapSymbolicationStatus(store.getState()); + if (statuses[statuses.length - 1] !== current) { + statuses.push(current); + } + }); + + await store.dispatch(loadProfile(profile, { browserConnection })); + + // FETCHING is set when fetching starts and reverted to INACTIVE on + // DONE_SOURCE_MAP_FETCHING; SYMBOLICATING is set when the worker starts + // and cleared by BULK_SOURCE_MAP_SYMBOLICATION on success. + expect(statuses).toEqual([ + 'INACTIVE', + 'FETCHING', + 'INACTIVE', + 'SYMBOLICATING', + 'INACTIVE', + ]); }); }); diff --git a/src/types/actions.ts b/src/types/actions.ts index 3847257421..205b5b4063 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -447,6 +447,10 @@ type ReceiveProfileAction = readonly type: 'UPDATE_PAGES'; readonly newPages: PageList; } + | { readonly type: 'START_SOURCE_MAP_FETCHING' } + | { readonly type: 'DONE_SOURCE_MAP_FETCHING' } + | { readonly type: 'START_SOURCE_MAP_SYMBOLICATION' } + | { readonly type: 'SOURCE_MAP_SYMBOLICATION_FAILED' } | { readonly type: 'BULK_SOURCE_MAP_SYMBOLICATION'; readonly newFuncTable: FuncTable; diff --git a/src/types/state.ts b/src/types/state.ts index 7e47f287ae..38eca24509 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -51,6 +51,10 @@ export type Reducer = (state: T | undefined, action: Action) => T; export type UploadedProfileInformation = ImportedUploadedProfileInformation; export type SymbolicationStatus = 'DONE' | 'SYMBOLICATING'; +export type SourceMapSymbolicationStatus = + | 'INACTIVE' + | 'FETCHING' + | 'SYMBOLICATING'; export type ThreadViewOptions = { readonly selectedNonInvertedCallNodePath: CallNodePath; readonly selectedInvertedCallNodePath: CallNodePath; @@ -94,6 +98,7 @@ export type ProfileViewState = { readonly viewOptions: { perThread: ThreadViewOptionsPerThreads; symbolicationStatus: SymbolicationStatus; + sourceMapSymbolicationStatus: SourceMapSymbolicationStatus; waitingForLibs: Set; previewSelection: PreviewSelection | null; scrollToSelectionGeneration: number; From c88bae9fd91f6b77a630eecb4a8b5a07354ed517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 5 May 2026 19:46:57 +0200 Subject: [PATCH 11/16] Check if fileNameStrIndex is undefined I noticed this bug while testing the source map PR locally. I was doing this: - Capture a profile with the JS sources feature enabled that includes source maps. - After the symbolication and source fetching, double click on a JS function that is source mapped. - Refresh the page. I was expecting it to work, but it was throwing errors because the source that I already opened wasn't in the profile and string table anymore (since it's added after the source map symbolication). So this fixes this issue by checking if the string table index is undefined --- src/selectors/profile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/selectors/profile.ts b/src/selectors/profile.ts index f5d13060f5..a2ad836a10 100644 --- a/src/selectors/profile.ts +++ b/src/selectors/profile.ts @@ -1102,7 +1102,7 @@ export const getSourceViewFile: Selector = createSelector( } const fileNameStrIndex = sources.filename[sourceIndex]; - return fileNameStrIndex !== null + return fileNameStrIndex !== null && fileNameStrIndex !== undefined ? stringTable.getString(fileNameStrIndex) : null; } From 186d486a0df303589fb1d2b18eb66fbb0a6fb95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Sat, 23 May 2026 10:15:54 +0200 Subject: [PATCH 12/16] Add a comment to run source map symbolication on the main thread for debugging It proved somewhat difficult when I had the debug the source map symbolication worker on the worker thread. It makes things a lot easier when it runs on the main thread instead. I tried adding a const flag for this with its proper function, but esbuild wasn't properly removing the dead code, so it was inflating the bundle a lot when this function was there even if it was unused. I tried also converting that into an async import, which extracted the symbolication into a different bundle, but it was still outputting the extra bundle even if it was false constant value. So, I decided to include it as a commented out documentation in case we need it in the future. It's not the best, but it should still help us when we need to debug the worker in the future. --- src/actions/source-map-symbolication.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/actions/source-map-symbolication.ts b/src/actions/source-map-symbolication.ts index f8ba000b86..b9a5074d22 100644 --- a/src/actions/source-map-symbolication.ts +++ b/src/actions/source-map-symbolication.ts @@ -76,6 +76,29 @@ export function doSourceMapSymbolication( * Spawn a one-shot source map worker, send it the input, and return the * output. The worker is terminated once a response is received or an error * occurs. Uses the same on-demand spawn pattern as gz.browser.ts. + * + * Debugging tip: to step through symbolication from the page DevTools, paste + * the snippet below over this function body. It runs the same core on the + * main thread (blocking, debug only). + * + * const { runSourceMapSymbolicationCore } = await import( + * 'firefox-profiler/profile-logic/source-map-symbolication' + * ); + * const sources = { + * length: input.sources.length, + * id: input.sources.id.slice(), + * filename: input.sources.filename.slice(), + * startLine: input.sources.startLine.slice(), + * startColumn: input.sources.startColumn.slice(), + * sourceMapURL: input.sources.sourceMapURL.slice(), + * content: input.sources.content.slice(), + * }; + * const stringArray = input.stringArray.slice(); + * const wasmUrl = new URL('/mappings.wasm', window.location.href).href; + * return runSourceMapSymbolicationCore( + * { ...input, sources, stringArray }, + * wasmUrl + * ); */ function _runSourceMapWorker(input: WorkerInput): Promise { return new Promise((resolve) => { From 9659768ac9153630e289cf91c7b5a76511a014d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Sat, 23 May 2026 12:16:52 +0200 Subject: [PATCH 13/16] Show source-mapped source in `profiler-cli function annotate` `fetchSourceAnnotation` was always reading from funcTable.source[funcIndex], which points at the compiled bundle. For JS-symbolicated functions the original file already lives behind funcTable.sourceMapInfo, and its text is typically embedded on the shared source table either by sourcesContent during symbolication or by the publish-with-sources path. Resolve the effective source through getOriginalPositionForFrame so the filename, line hit attribution, and content lookup all target the original source when sourceMapInfo is present. When sources.content is populated, use it directly and skip the network fetch entirely so the source view works offline for published-with-sources profiles. --- src/profile-query/function-annotate.ts | 56 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/profile-query/function-annotate.ts b/src/profile-query/function-annotate.ts index e50c86a323..9a98cc64e6 100644 --- a/src/profile-query/function-annotate.ts +++ b/src/profile-query/function-annotate.ts @@ -20,6 +20,7 @@ import { getNativeSymbolInfo, getNativeSymbolsForFunc, findAddressProofForFile, + getOriginalPositionForFrame, } from 'firefox-profiler/profile-logic/profile-data'; import { fetchAssembly } from 'firefox-profiler/utils/fetch-assembly'; import { fetchSource } from 'firefox-profiler/utils/fetch-source'; @@ -76,8 +77,8 @@ async function fetchSourceAnnotation( contextOption: string ): Promise { const warnings: string[] = []; - const sourceIndex = profile.shared.funcTable.source[funcIndex]; - if (sourceIndex === null) { + const compiledSourceIndex = profile.shared.funcTable.source[funcIndex]; + if (compiledSourceIndex === null) { if (mode === 'src') { warnings.push( `Function ${functionHandle} has no source index. Use --mode asm for assembly view.` @@ -93,10 +94,25 @@ async function fetchSourceAnnotation( samples, originalLocation, } = thread; + + // For JS-symbolicated funcs, prefer the original source from originalLocation + // so the source view shows the original file (filename, content, line/column + // hits) rather than the compiled bundle. Falls back to the compiled source + // when no source map info is present. + const { source: resolvedSourceIndex } = getOriginalPositionForFrame( + null, + funcIndex, + frameTable, + threadFuncTable, + originalLocation + ); + const sourceIndex = resolvedSourceIndex ?? compiledSourceIndex; + const filename = thread.stringTable.getString( thread.sources.filename[sourceIndex] ); const sourceUuid = thread.sources.id[sourceIndex]; + const inlineContent = thread.sources.content[sourceIndex]; const stackLineInfo = getStackLineInfo( stackTable, @@ -132,22 +148,30 @@ async function fetchSourceAnnotation( let fileLines: string[] | null = null; let totalFileLines: number | null = null; - const fetchResult = await fetchSource( - filename, - sourceUuid, - symbolServerUrl, - addressProof, - archiveCache, - nodeDelegate - ); - if (fetchResult.type === 'SUCCESS') { - fileLines = fetchResult.source.split('\n'); + if (inlineContent !== null) { + // Source content was embedded in the profile (e.g. captured from a source + // map's sourcesContent during JS symbolication). Use it directly so the + // source view works offline. + fileLines = inlineContent.split('\n'); totalFileLines = fileLines.length; } else { - const errorMessages = fetchResult.errors - .map((e) => JSON.stringify(e)) - .join('; '); - warnings.push(`Could not fetch source for ${filename}: ${errorMessages}`); + const fetchResult = await fetchSource( + filename, + sourceUuid, + symbolServerUrl, + addressProof, + archiveCache, + nodeDelegate + ); + if (fetchResult.type === 'SUCCESS') { + fileLines = fetchResult.source.split('\n'); + totalFileLines = fileLines.length; + } else { + const errorMessages = fetchResult.errors + .map((e) => JSON.stringify(e)) + .join('; '); + warnings.push(`Could not fetch source for ${filename}: ${errorMessages}`); + } } const annotatedLineNums = new Set([ From 7cc1eaeca35c443cf8dfb4643e5b1aa2d704e294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Thu, 30 Apr 2026 13:34:27 +0200 Subject: [PATCH 14/16] Always sanitize the source contents during profile sanitization --- src/profile-logic/sanitize.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/profile-logic/sanitize.ts b/src/profile-logic/sanitize.ts index dec2160f55..a69bec579f 100644 --- a/src/profile-logic/sanitize.ts +++ b/src/profile-logic/sanitize.ts @@ -126,6 +126,14 @@ export function sanitizePII( stringArray, }; + // FIXME: Currently we always sanitize the source contents while publishing. + // But we should have a sharing option in the publish panel to be able to + // include sources. + newShared.sources = { + ...newShared.sources, + content: Array(newShared.sources.content.length).fill(null), + }; + let stackFlags: Uint8Array | null = null; if (windowIdFromPrivateBrowsing.size > 0) { From 5a6dfd7df440b3b3d874634546e26e017f6f065d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Wed, 27 May 2026 16:07:28 +0200 Subject: [PATCH 15/16] Make JS source-map symbolication race-safe and run it in parallel with native Refactor the source-map worker to return position-keyed resolutions instead of full table snapshots. The main thread applies the response against the current shared state via applySourceMapSymbolicationResponse, allocating sourceMapInfo rows, interning names, and deduping URLs against whatever native symbolication has committed by then. With per-funcIndex/per-frameIndex idempotency (skip rows already populated) and JS funcs/frames being insulated from native symbolication, the worker no longer needs to wait for native symbolication to finish. finalizeProfileView now runs source-map fetch, native symbolication, and the source-map worker all in parallel. --- src/actions/receive-profile.ts | 22 +- src/actions/source-map-symbolication.ts | 44 ++- src/profile-logic/source-map-symbolication.ts | 364 ++++++++++++------ src/profile-logic/source-map-worker-types.ts | 60 ++- 4 files changed, 318 insertions(+), 172 deletions(-) diff --git a/src/actions/receive-profile.ts b/src/actions/receive-profile.ts index 77c5b1028e..b8c980f030 100644 --- a/src/actions/receive-profile.ts +++ b/src/actions/receive-profile.ts @@ -248,19 +248,19 @@ export function finalizeProfileView( } } - // Fetch source maps for all JS sources with a sourceMapURL, then apply JS - // symbolication. Fetching runs in parallel with native symbolication, but - // the worker is only dispatched after native symbolication has committed - // its Redux changes, so neither clobbers the other's funcTable updates. - // Requires WebChannel version 7+. + // Fetch source maps for all JS sources with a sourceMapURL, then run the + // source-map worker. Runs fully in parallel with native symbolication: + // native only touches funcs/frames belonging to library resources (JS + // funcs aren't in those sets), and the JS apply step reads current + // shared state at dispatch time so it composes with whatever native + // has committed by then. Requires WebChannel version 7+. let sourceMapSymbolicationPromise: Promise | null = null; if (browserConnection !== null && browserConnection.supportsGetSourceMap) { - // Fetch source maps concurrently with native symbolication. Once both - // have completed, apply JS symbolication on top of the final state. - sourceMapSymbolicationPromise = Promise.all([ - doResolveSourceMaps(profile, browserConnection, dispatch), - symbolicationPromise, - ]).then(([{ resolvedSourceMaps, compiledSources }]) => + sourceMapSymbolicationPromise = doResolveSourceMaps( + profile, + browserConnection, + dispatch + ).then(({ resolvedSourceMaps, compiledSources }) => dispatch(doSourceMapSymbolication(resolvedSourceMaps, compiledSources)) ); } diff --git a/src/actions/source-map-symbolication.ts b/src/actions/source-map-symbolication.ts index b9a5074d22..7bb309a77a 100644 --- a/src/actions/source-map-symbolication.ts +++ b/src/actions/source-map-symbolication.ts @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { getRawProfileSharedData } from 'firefox-profiler/selectors'; +import { applySourceMapSymbolicationResponse } from 'firefox-profiler/profile-logic/source-map-symbolication'; import type { WorkerInput, @@ -49,16 +50,29 @@ export function doSourceMapSymbolication( dispatch({ type: 'START_SOURCE_MAP_SYMBOLICATION' }); const result = await _runSourceMapWorker(input); switch (result.type) { - case 'success': + case 'success': { + // Apply against the current shared state (not the snapshot the worker + // received), so concurrent worker runs compose instead of stomping + // each other's results. + const currentShared = getRawProfileSharedData(getState()); + const applied = applySourceMapSymbolicationResponse( + currentShared, + result.response + ); + if (applied === null) { + dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' }); + break; + } dispatch({ type: 'BULK_SOURCE_MAP_SYMBOLICATION', - newFuncTable: result.newFuncTable, - newFrameTable: result.newFrameTable, - newOriginalLocation: result.newOriginalLocation, - newSources: result.newSources, - newStringArray: result.newStringArray, + newFuncTable: applied.newFuncTable, + newFrameTable: applied.newFrameTable, + newOriginalLocation: applied.newOriginalLocation, + newSources: applied.newSources, + newStringArray: applied.newStringArray, }); break; + } case 'error': console.warn('Source map worker error:', result.message); dispatch({ type: 'SOURCE_MAP_SYMBOLICATION_FAILED' }); @@ -79,26 +93,14 @@ export function doSourceMapSymbolication( * * Debugging tip: to step through symbolication from the page DevTools, paste * the snippet below over this function body. It runs the same core on the - * main thread (blocking, debug only). + * main thread (blocking, debug only). The worker no longer mutates its + * input, so no defensive cloning is needed here. * * const { runSourceMapSymbolicationCore } = await import( * 'firefox-profiler/profile-logic/source-map-symbolication' * ); - * const sources = { - * length: input.sources.length, - * id: input.sources.id.slice(), - * filename: input.sources.filename.slice(), - * startLine: input.sources.startLine.slice(), - * startColumn: input.sources.startColumn.slice(), - * sourceMapURL: input.sources.sourceMapURL.slice(), - * content: input.sources.content.slice(), - * }; - * const stringArray = input.stringArray.slice(); * const wasmUrl = new URL('/mappings.wasm', window.location.href).href; - * return runSourceMapSymbolicationCore( - * { ...input, sources, stringArray }, - * wasmUrl - * ); + * return runSourceMapSymbolicationCore(input, wasmUrl); */ function _runSourceMapWorker(input: WorkerInput): Promise { return new Promise((resolve) => { diff --git a/src/profile-logic/source-map-symbolication.ts b/src/profile-logic/source-map-symbolication.ts index 261179577f..1b5234b42d 100644 --- a/src/profile-logic/source-map-symbolication.ts +++ b/src/profile-logic/source-map-symbolication.ts @@ -92,12 +92,19 @@ import type { IndexIntoFrameTable, FuncTable, FrameTable, + RawProfileSharedData, SourceLocationTable, SourceTable, } from '../types'; import type { NullableMappedPosition } from 'source-map'; import type { SourceMapConsumer } from './source-map-store'; -import type { WorkerInput, WorkerOutput } from './source-map-worker-types'; +import type { + FrameResolution, + FuncResolution, + SourceMapSymbolicationResponse, + WorkerInput, + WorkerOutput, +} from './source-map-worker-types'; const INT32_MAX = 0x7fffffff; @@ -116,15 +123,13 @@ type ParsedSource = { lineOffsets: number[]; }; -export type SourceMapSymbolicationResult = { - newFuncTable: FuncTable; - newFrameTable: FrameTable; - newOriginalLocation: SourceLocationTable; -}; - // The subset of RawProfileSharedData that symbolicateWithSourceMaps reads. // Other shared fields (stackTable, resourceTable, nativeSymbols) are not // touched, so callers (e.g. the worker) can supply just this subset. +// +// The worker only *reads* these fields. All table mutations happen on the +// main thread in applySourceMapSymbolicationResponse, which builds new +// tables from the current shared state at apply time. export type SourceMapSymbolicationInput = { frameTable: FrameTable; funcTable: FuncTable; @@ -145,13 +150,15 @@ export type SourceMapSymbolicationInput = { * name resolution falls back to original-source-only lookup via * `sourcesContent`. * - * Returns null if no mappings were applied. + * Returns a position-keyed response that the main thread translates into + * new tables via applySourceMapSymbolicationResponse. Returns null if no + * funcs or frames produced a resolution. */ export function symbolicateWithSourceMaps( shared: SourceMapSymbolicationInput, sourceMapStore: SourceMapStore, compiledSources: Map = new Map() -): SourceMapSymbolicationResult | null { +): SourceMapSymbolicationResponse | null { const { funcsToSymbolicate, framesToSymbolicate } = _identifyToSymbolicate( shared.frameTable, shared.funcTable, @@ -162,7 +169,7 @@ export function symbolicateWithSourceMaps( return null; } - return _applySourceMapSymbolication( + return _buildSourceMapSymbolicationResponse( shared, funcsToSymbolicate, framesToSymbolicate, @@ -289,56 +296,39 @@ function _isExactSourceMapEntry( } /** - * Apply source map lookups to produce new funcTable, frameTable, and - * originalLocation table. Positions with no mapping in the source map are skipped. - * Returns null if no mappings were actually applied (e.g. no consumers available). + * Walk the funcs/frames to symbolicate, run source-map lookups, and collect + * the results into a position-keyed response. The worker doesn't allocate + * source-table or originalLocation indices and doesn't intern strings; that + * bookkeeping happens main-side in applySourceMapSymbolicationResponse. + * + * Returns null if no funcs or frames were resolved (e.g. no consumers were + * available, or every position fell outside its source map's coverage). */ -function _applySourceMapSymbolication( +function _buildSourceMapSymbolicationResponse( shared: SourceMapSymbolicationInput, funcsToSymbolicate: IndexIntoFuncTable[], framesToSymbolicate: IndexIntoFrameTable[], sourceMapStore: SourceMapStore, compiledSources: Map -): SourceMapSymbolicationResult | null { - const { frameTable, funcTable, originalLocation, sources, stringArray } = - shared; - - // funcTable, frameTable, and originalLocation are cloned because we overwrite - // values at existing indices (e.g. funcTable.name[i], frameTable.originalLocation[i]). - // - // sources and stringArray are *not* cloned: they're append-only here - // (_findOrCreateSource pushes new entries, StringTable.withBackingArray pushes - // newly interned strings). This function only runs inside the source map worker, - // which received its own structured-cloned copies, so mutating them in place - // is safe for the main thread, and the worker hands the mutated arrays back - // through WorkerOutput. - const newFuncTable = shallowCloneFuncTable(funcTable); - const newFrameTable = shallowCloneFrameTable(frameTable); - const newOriginalLocation = shallowCloneSourceLocationTable(originalLocation); +): SourceMapSymbolicationResponse | null { + const { frameTable, funcTable } = shared; - const stringTable = StringTable.withBackingArray(stringArray); + const funcResults = new Map(); + const frameResults = new Map(); - // filename string index to source index, covering all existing null-id - // (original source) entries. Updated in place as new entries are appended. - const sourceByFilename = new Map(); - for (let i = 0; i < sources.length; i++) { - if (sources.id[i] === null) { - sourceByFilename.set(sources.filename[i], i); - } - } + // Per-URL source content fetched from source maps' sourcesContent. Used + // both by original-source name resolution and (post-walk) to populate the + // response's originalSources map. Value null means we probed and the + // source map had no content for this URL. + const originalSourceContents = new Map(); const compiledSourceCache = new Map(); - // Parsed scope tree + line offsets of original sources we've seen. Used to - // recover function names stripped during minification (esbuild drops - // named-function-expression identifiers when the inner name isn't referenced - // from inside the function body, so `function mapToPropsProxy()` becomes - // `function()`. Only the original source still knows the name). Map value - // `null` is cached when no content was available so we don't keep retrying. - const originalSourceCache = new Map< - IndexIntoSourceTable, - ParsedSource | null - >(); + // Parsed scope tree + line offsets of original sources we've seen, keyed + // by URL. Used to recover function names stripped during minification. + // Value `null` is cached when no content was available so we don't keep + // retrying. + const originalSourceCache = new Map(); // Function definitions. These get an original source file. for (const funcIndex of funcsToSymbolicate) { @@ -359,15 +349,11 @@ function _applySourceMapSymbolication( consumer, line, column, - sources, - stringTable, - sourceByFilename, - newOriginalLocation + originalSourceContents ); if (remap === null) { continue; } - newFuncTable.originalLocation[funcIndex] = remap.originalLocationIndex; // Full resolution (compiled + original, with ancestor chain) when the // compiled bundle is available. Otherwise (e.g. the profile was captured @@ -392,21 +378,23 @@ function _applySourceMapSymbolication( column, compiled.lineOffsets, compiled.tree, - sources, - remap.originalSourceIndex, + originalSourceContents, originalSourceCache ); } else { resolvedName = _resolveOriginalName( - sources, - remap.originalSourceIndex, remap.original, + originalSourceContents, originalSourceCache ); } - if (resolvedName) { - newFuncTable.name[funcIndex] = stringTable.indexForString(resolvedName); - } + + funcResults.set(funcIndex, { + originalSource: remap.originalSource, + originalLine: remap.originalLine, + originalColumn: remap.originalColumn, + name: resolvedName, + }); } // Frame execution points. Remap line/column and capture the original source @@ -431,28 +419,37 @@ function _applySourceMapSymbolication( consumer, line, column, - sources, - stringTable, - sourceByFilename, - newOriginalLocation + originalSourceContents ); if (remap === null) { continue; } - newFrameTable.originalLocation[frameIndex] = remap.originalLocationIndex; + frameResults.set(frameIndex, { + originalSource: remap.originalSource, + originalLine: remap.originalLine, + originalColumn: remap.originalColumn, + }); } - if (newOriginalLocation.length === originalLocation.length) { + if (funcResults.size === 0 && frameResults.size === 0) { return null; } - return { newFuncTable, newFrameTable, newOriginalLocation }; + const originalSources = new Map(); + for (const [url, content] of originalSourceContents) { + originalSources.set(url, { content }); + } + + return { funcResults, frameResults, originalSources }; } /** - * Remap a single (line, column) through the source map and append a new row - * to newOriginalLocation. Returns the new row index plus the resolved original - * position. Returns null when the source map has no mapping at that position. + * Remap a single (line, column) through the source map. Returns the original + * source URL plus 1-based line/column (Gecko convention), or null when the + * source map has no mapping at that position. Also populates + * `originalSourceContents` with the source map's sourcesContent for the URL + * the first time it's seen, used by original-source name resolution and + * later shipped to the main thread in the response's originalSources map. * * Gecko line/column are 1-based. source-map expects 0-based columns and * returns 0-based columns; we convert in both directions here so callers @@ -462,44 +459,35 @@ function _remapPosition( consumer: SourceMapConsumer, line: number, column: number, - sources: SourceTable, - stringTable: StringTable, - sourceByFilename: Map, - newOriginalLocation: SourceLocationTable + originalSourceContents: Map ): { - originalLocationIndex: number; - originalSourceIndex: IndexIntoSourceTable; + originalSource: string; + originalLine: number; + originalColumn: number; original: OriginalPosition; } | null { const original = consumer.originalPositionFor({ line, column: column - 1 }); if (original.source === null || original.line === null) { return null; } - const originalSourceIndex = _findOrCreateSource( - sources, - stringTable, - original.source, - sourceByFilename - ); - // Extract original source content from sourcesContent if not yet seen. + // Extract original source content from sourcesContent if not yet seen, or + // re-probe when a prior consumer recorded null but this one might have it. // nullOnMissing=true so we get null instead of a throw when not embedded. - if (sources.content[originalSourceIndex] === null) { + const existingContent = originalSourceContents.get(original.source); + if (existingContent === undefined || existingContent === null) { const content = consumer.sourceContentFor(original.source, true); if (content !== null) { - sources.content[originalSourceIndex] = content; + originalSourceContents.set(original.source, content); + } else if (existingContent === undefined) { + originalSourceContents.set(original.source, null); } } - const originalLocationIndex = newOriginalLocation.length; - newOriginalLocation.source.push(originalSourceIndex); - newOriginalLocation.line.push(original.line); - newOriginalLocation.column.push((original.column ?? 0) + 1); - newOriginalLocation.length++; - return { - originalLocationIndex, - originalSourceIndex, + originalSource: original.source, + originalLine: original.line, + originalColumn: (original.column ?? 0) + 1, original: original as OriginalPosition, }; } @@ -539,9 +527,8 @@ function _resolveFunctionName( column: number, lineOffsets: number[], scopeTree: FunctionScope[], - sources: SourceTable, - originalSourceIndex: IndexIntoSourceTable, - originalSourceCache: Map + originalSourceContents: Map, + originalSourceCache: Map ): string | null { // 1. Original-source name lookup. Prefers the function's own declared // identifier, falling back to the verbatim LHS text of the surrounding @@ -551,9 +538,8 @@ function _resolveFunctionName( // the enclosing function, matching SpiderMonkey's output shape (e.g. // `outer/Foo.prototype.bar`). const originalName = _resolveOriginalName( - sources, - originalSourceIndex, original, + originalSourceContents, originalSourceCache ); if (originalName !== null && !_isMemberStyleName(originalName)) { @@ -789,20 +775,19 @@ function _resolveCompiledName( * an `astName` nor an `lhsText`. */ function _resolveOriginalName( - sources: SourceTable, - originalSourceIndex: IndexIntoSourceTable, original: OriginalPosition, - cache: Map + originalSourceContents: Map, + cache: Map ): string | null { // A cached `null` means we saw no content the last time we looked, but a - // later _remapPosition from a different consumer may have populated it - // since. Re-check `sources.content` on every cached miss so the upgrade - // doesn't depend on iteration order across consumers. - let entry = cache.get(originalSourceIndex); + // later _remapPosition from a different consumer may have populated + // `originalSourceContents` since. Re-check on every cached miss so the + // upgrade doesn't depend on iteration order across consumers. + let entry = cache.get(original.source); if (entry === undefined || entry === null) { - const sourceContent = sources.content[originalSourceIndex]; + const sourceContent = originalSourceContents.get(original.source) ?? null; if (sourceContent === null) { - cache.set(originalSourceIndex, null); + cache.set(original.source, null); return null; } entry = { @@ -812,7 +797,7 @@ function _resolveOriginalName( ), lineOffsets: buildLineOffsets(sourceContent), }; - cache.set(originalSourceIndex, entry); + cache.set(original.source, entry); } // source-map's original.line is 1-based, column is 0-based. const offset = lineColToOffset( @@ -890,13 +875,11 @@ function _findOrCreateSource( /** * End-to-end source map symbolication runner: build the SourceMapStore, run - * symbolication, and return a WorkerOutput. + * symbolication, and return a WorkerOutput carrying a position-keyed response. * - * Mutates `input.sources` and `input.stringArray` in place - * (`_findOrCreateSource` appends new sources, `StringTable.withBackingArray` - * appends interned strings). Callers must pass copies if the originals must - * stay untouched. The worker is safe because structured clone already copies - * them at the thread boundary. + * The worker only reads from `input` — no tables or string arrays are mutated. + * The main thread translates the response into new tables via + * applySourceMapSymbolicationResponse, against the latest shared state. */ export async function runSourceMapSymbolicationCore( input: WorkerInput, @@ -915,24 +898,17 @@ export async function runSourceMapSymbolicationCore( } = input; sourceMapStore = await SourceMapStore.create(resolvedSourceMaps, wasmUrl); - const result = symbolicateWithSourceMaps( + const response = symbolicateWithSourceMaps( { frameTable, funcTable, originalLocation, sources, stringArray }, sourceMapStore, compiledSources ); - if (result === null) { + if (response === null) { return { type: 'no-op' }; } - return { - type: 'success', - newFuncTable: result.newFuncTable, - newFrameTable: result.newFrameTable, - newOriginalLocation: result.newOriginalLocation, - newSources: sources, - newStringArray: stringArray, - }; + return { type: 'success', response }; } catch (err) { return { type: 'error', @@ -944,3 +920,141 @@ export async function runSourceMapSymbolicationCore( } } } + +/** + * Apply a worker-produced SourceMapSymbolicationResponse against the current + * shared tables and return new ones. This is the main-thread complement to + * the worker: it allocates the source-table and originalLocation indices, + * interns names into the string table, and dedupes original-source URLs + * against current state. + * + * Reading current state at apply time (rather than reusing the snapshot the + * worker started from) makes concurrent worker runs compose correctly: a + * second result landing after a first never undoes the first's writes, + * because each apply step skips funcs/frames that already have an + * originalLocation row. + * + * Idempotency relies on funcIndex/frameIndex being stable across concurrent + * runs. JS source-map symbolication only appends to originalLocation, + * sources, and stringArray — it never adds funcs or frames. If a future + * change adds funcs/frames during symbolication, this contract needs + * revisiting. + * + * Returns null when nothing applied (every response entry collided with + * a row that's already populated). + */ +export function applySourceMapSymbolicationResponse( + shared: RawProfileSharedData, + response: SourceMapSymbolicationResponse +): { + newFuncTable: FuncTable; + newFrameTable: FrameTable; + newOriginalLocation: SourceLocationTable; + newSources: SourceTable; + newStringArray: string[]; +} | null { + const { funcTable, frameTable, originalLocation, sources, stringArray } = + shared; + + const newFuncTable = shallowCloneFuncTable(funcTable); + const newFrameTable = shallowCloneFrameTable(frameTable); + const newOriginalLocation = shallowCloneSourceLocationTable(originalLocation); + const newSources = _shallowCloneSourceTable(sources); + const newStringArray = stringArray.slice(); + const stringTable = StringTable.withBackingArray(newStringArray); + + // filename string index to source index, covering all existing null-id + // (original source) entries. Updated in place as new entries are appended + // by _findOrCreateSource. + const sourceByFilename = new Map(); + for (let i = 0; i < newSources.length; i++) { + if (newSources.id[i] === null) { + sourceByFilename.set(newSources.filename[i], i); + } + } + + // Resolve each original-source URL to a source-table index, creating new + // entries as needed and upgrading content where the existing entry has + // none. + const urlToSourceIndex = new Map(); + for (const [url, { content }] of response.originalSources) { + const sourceIndex = _findOrCreateSource( + newSources, + stringTable, + url, + sourceByFilename + ); + if (content !== null && newSources.content[sourceIndex] === null) { + newSources.content[sourceIndex] = content; + } + urlToSourceIndex.set(url, sourceIndex); + } + + let applied = 0; + + for (const [funcIndex, resolution] of response.funcResults) { + // A concurrent run got here first. Skip the whole entry: the row and + // the name go together; writing a name without an originalLocation row + // would be inconsistent. + if (newFuncTable.originalLocation[funcIndex] !== null) { + continue; + } + const sourceIndex = urlToSourceIndex.get(resolution.originalSource); + if (sourceIndex === undefined) { + continue; + } + const rowIndex = newOriginalLocation.length; + newOriginalLocation.source.push(sourceIndex); + newOriginalLocation.line.push(resolution.originalLine); + newOriginalLocation.column.push(resolution.originalColumn); + newOriginalLocation.length++; + newFuncTable.originalLocation[funcIndex] = rowIndex; + if (resolution.name !== null) { + newFuncTable.name[funcIndex] = stringTable.indexForString( + resolution.name + ); + } + applied++; + } + + for (const [frameIndex, resolution] of response.frameResults) { + if (newFrameTable.originalLocation[frameIndex] !== null) { + continue; + } + const sourceIndex = urlToSourceIndex.get(resolution.originalSource); + if (sourceIndex === undefined) { + continue; + } + const rowIndex = newOriginalLocation.length; + newOriginalLocation.source.push(sourceIndex); + newOriginalLocation.line.push(resolution.originalLine); + newOriginalLocation.column.push(resolution.originalColumn); + newOriginalLocation.length++; + newFrameTable.originalLocation[frameIndex] = rowIndex; + applied++; + } + + if (applied === 0) { + return null; + } + + return { + newFuncTable, + newFrameTable, + newOriginalLocation, + newSources, + newStringArray, + }; +} + +function _shallowCloneSourceTable(sources: SourceTable): SourceTable { + return { + id: sources.id.slice(), + filename: sources.filename.slice(), + startLine: sources.startLine.slice(), + startColumn: sources.startColumn.slice(), + sourceMapURL: sources.sourceMapURL.slice(), + content: sources.content.slice(), + length: sources.length, + }; +} diff --git a/src/profile-logic/source-map-worker-types.ts b/src/profile-logic/source-map-worker-types.ts index f54a94f9e0..d0487138fa 100644 --- a/src/profile-logic/source-map-worker-types.ts +++ b/src/profile-logic/source-map-worker-types.ts @@ -3,11 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { - FuncTable, - FrameTable, + IndexIntoFuncTable, + IndexIntoFrameTable, IndexIntoSourceTable, - SourceLocationTable, - SourceTable, } from 'firefox-profiler/types'; import type { RawSourceMap } from 'source-map'; import type { SourceMapSymbolicationInput } from './source-map-symbolication'; @@ -23,18 +21,50 @@ export type WorkerInput = SourceMapSymbolicationInput & { }; /** - * Data sent back from the worker to the main thread. - * On success, the new tables replace the profile's shared tables in Redux state. + * Resolved original location for a single frame. Line/column are 1-based + * (Gecko convention). `originalSource` is a URL string; the apply step on + * the main thread looks it up against the current sources table. */ +export type FrameResolution = { + originalSource: string; + originalLine: number; + originalColumn: number; +}; + +/** + * Resolved original location plus optional function name for a single func. + * `name` is null when no name could be determined, in which case the existing + * funcTable.name entry is kept. + */ +export type FuncResolution = FrameResolution & { + name: string | null; +}; + +/** + * Opaque, position-keyed worker result. The apply step on the main thread + * (applySourceMapSymbolicationResponse) consumes this plus the current shared + * tables to produce new ones. + * + * Modeled like a symbolication API response: the worker doesn't allocate + * source-table or originalLocation indices, and doesn't intern strings. That + * bookkeeping happens main-side against the latest state, so concurrent + * worker runs compose correctly instead of one's stale snapshot stomping + * another's result. + */ +export type SourceMapSymbolicationResponse = { + // Funcs the worker resolved. Funcs not in this map keep their existing data. + // JS source-map symbolication never adds funcs, so funcIndex stays stable + // across concurrent runs. + funcResults: Map; + // Same idea for frames. + frameResults: Map; + // Original sources discovered during symbolication, keyed by URL. content + // is null when the source map didn't carry sourcesContent for this URL. + // The apply step dedupes URLs against the current sources table. + originalSources: Map; +}; + export type WorkerOutput = - | { - type: 'success'; - newFuncTable: FuncTable; - newFrameTable: FrameTable; - newOriginalLocation: SourceLocationTable; - // Sources and stringArray may have new entries appended by _findOrCreateSource. - newSources: SourceTable; - newStringArray: string[]; - } + | { type: 'success'; response: SourceMapSymbolicationResponse } | { type: 'no-op' } | { type: 'error'; message: string }; From f77359ed3a415b20bc622934d0f6ead0678cd1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Wed, 27 May 2026 17:08:01 +0200 Subject: [PATCH 16/16] Inline source/line resolution in line-timings hot loop getStackLineInfo runs once per stack in the stack table, which can mean hundreds of thousands of iterations on large profiles. Calling getOriginalPositionForFrame in that loop allocated a fresh {source, line, column} object per stack and always resolved the line (and column) even though the line is only used when the stack's source matches the source view, and the column is never used here. Inline the 3-tier source-map fallback so we drop the per-stack allocation, skip the column entirely, and defer the line lookup until matchesSource is true. Apply the same inlining to getTotalLineTimingsForCallNode, which has the same shape and only needs the line. Measured on a large profile: getStackLineInfo went from ~373ms back to ~199ms. --- src/profile-logic/line-timings.ts | 63 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/profile-logic/line-timings.ts b/src/profile-logic/line-timings.ts index e8eb3c3f49..aa9993b3ea 100644 --- a/src/profile-logic/line-timings.ts +++ b/src/profile-logic/line-timings.ts @@ -15,7 +15,6 @@ import type { SourceLocationTable, } from 'firefox-profiler/types'; import { SetCollectionBuilder } from 'firefox-profiler/utils/set-collection'; -import { getOriginalPositionForFrame } from './profile-data'; /** * For each stack in `stackTable`, and one specific source file, compute the @@ -62,20 +61,37 @@ export function getStackLineInfo( const frame = stackTable.frame[stackIndex]; const func = frameTable.func[frame]; - const { source: sourceIndexOfThisStack, line: selfLineFromMapping } = - getOriginalPositionForFrame( - frame, - func, - frameTable, - funcTable, - originalLocation - ); + + // Inlined source resolution from getOriginalPositionForFrame. We avoid + // its per-stack {source, line, column} allocation, skip resolving the + // column entirely, and defer the line lookup until we know the source + // matches. + const frameOriginalLocationIdx = frameTable.originalLocation[frame]; + const funcOriginalLocationIdx = funcTable.originalLocation[func]; + let sourceIndexOfThisStack; + if (frameOriginalLocationIdx !== null) { + sourceIndexOfThisStack = + originalLocation.source[frameOriginalLocationIdx]; + } else if (funcOriginalLocationIdx !== null) { + sourceIndexOfThisStack = originalLocation.source[funcOriginalLocationIdx]; + } else { + sourceIndexOfThisStack = funcTable.source[func]; + } const matchesSource = sourceIndexOfThisStack === sourceViewSourceIndex; if (prefixLineSet === -1 && !matchesSource) { stackIndexToLineSetIndex[stackIndex] = -1; } else { - const selfLineOrNull = matchesSource ? selfLineFromMapping : null; + let selfLineOrNull: number | null = null; + if (matchesSource) { + if (frameOriginalLocationIdx !== null) { + selfLineOrNull = originalLocation.line[frameOriginalLocationIdx]; + } else if (funcOriginalLocationIdx !== null) { + selfLineOrNull = originalLocation.line[funcOriginalLocationIdx]; + } else { + selfLineOrNull = frameTable.line[frame] ?? funcTable.lineNumber[func]; + } + } stackIndexToLineSetIndex[stackIndex] = builder.extend( prefixLineSet !== -1 ? prefixLineSet : null, @@ -193,17 +209,24 @@ export function getTotalLineTimingsForCallNode( continue; } - // Resolve in the same coordinate space as the source view: helper returns - // a source-mapped line when available, otherwise the frame's compiled - // line, otherwise the func's compiled line. + // Resolve in the same coordinate space as the source view: prefer the + // source-mapped line, otherwise the frame's compiled line, otherwise the + // func's compiled line. Inlined from getOriginalPositionForFrame to avoid + // the per-sample object allocation. const funcIndex = frameTable.func[callNodeFrame]; - const { line: frameLine } = getOriginalPositionForFrame( - callNodeFrame, - funcIndex, - frameTable, - funcTable, - originalLocation - ); + const frameOriginalLocationIdx = frameTable.originalLocation[callNodeFrame]; + let frameLine: number | null; + if (frameOriginalLocationIdx !== null) { + frameLine = originalLocation.line[frameOriginalLocationIdx]; + } else { + const funcOriginalLocationIdx = funcTable.originalLocation[funcIndex]; + if (funcOriginalLocationIdx !== null) { + frameLine = originalLocation.line[funcOriginalLocationIdx]; + } else { + frameLine = + frameTable.line[callNodeFrame] ?? funcTable.lineNumber[funcIndex]; + } + } const line = frameLine !== null ? frameLine : funcLine; if (line === null) { continue;