UNPKG

heap-snapshot-toolkit

Version:

Tools for parsing Chromium heap snapshot (`*.heapsnapshot`) files and doing useful things with them

1,443 lines (1,426 loc) 199 kB
/* Generated from devtools-frontend@138048f via build-devtools-frontend.sh. Source: https://github.com/ChromeDevTools/devtools-frontend/commit/138048f */ /* * Copyright (C) 2014 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const HeapSnapshotProgressEvent = { Update: 'ProgressUpdate', BrokenSnapshot: 'BrokenSnapshot', }; const baseSystemDistance = 100000000; const baseUnreachableDistance = baseSystemDistance * 2; class AllocationNodeCallers { nodesWithSingleCaller; branchingCallers; constructor(nodesWithSingleCaller, branchingCallers) { this.nodesWithSingleCaller = nodesWithSingleCaller; this.branchingCallers = branchingCallers; } } class SerializedAllocationNode { id; name; scriptName; scriptId; line; column; count; size; liveCount; liveSize; hasChildren; constructor(nodeId, functionName, scriptName, scriptId, line, column, count, size, liveCount, liveSize, hasChildren) { this.id = nodeId; this.name = functionName; this.scriptName = scriptName; this.scriptId = scriptId; this.line = line; this.column = column; this.count = count; this.size = size; this.liveCount = liveCount; this.liveSize = liveSize; this.hasChildren = hasChildren; } } class AllocationStackFrame { functionName; scriptName; scriptId; line; column; constructor(functionName, scriptName, scriptId, line, column) { this.functionName = functionName; this.scriptName = scriptName; this.scriptId = scriptId; this.line = line; this.column = column; } } class Node { id; name; distance; nodeIndex; retainedSize; selfSize; type; canBeQueried; detachedDOMTreeNode; isAddedNotRemoved; ignored; constructor(id, name, distance, nodeIndex, retainedSize, selfSize, type) { this.id = id; this.name = name; this.distance = distance; this.nodeIndex = nodeIndex; this.retainedSize = retainedSize; this.selfSize = selfSize; this.type = type; this.canBeQueried = false; this.detachedDOMTreeNode = false; this.isAddedNotRemoved = null; this.ignored = false; } } class Edge { name; node; type; edgeIndex; isAddedNotRemoved; constructor(name, node, type, edgeIndex) { this.name = name; this.node = node; this.type = type; this.edgeIndex = edgeIndex; this.isAddedNotRemoved = null; } } class Aggregate { count; distance; self; maxRet; name; idxs; } class AggregateForDiff { name; indexes; ids; selfSizes; constructor() { this.name = ''; this.indexes = []; this.ids = []; this.selfSizes = []; } } class Diff { name; addedCount; removedCount; addedSize; removedSize; deletedIndexes; addedIndexes; countDelta; sizeDelta; constructor(name) { this.name = name; this.addedCount = 0; this.removedCount = 0; this.addedSize = 0; this.removedSize = 0; this.deletedIndexes = []; this.addedIndexes = []; } } class DiffForClass { name; addedCount; removedCount; addedSize; removedSize; deletedIndexes; addedIndexes; countDelta; sizeDelta; } class ComparatorConfig { fieldName1; ascending1; fieldName2; ascending2; constructor(fieldName1, ascending1, fieldName2, ascending2) { this.fieldName1 = fieldName1; this.ascending1 = ascending1; this.fieldName2 = fieldName2; this.ascending2 = ascending2; } } class WorkerCommand { callId; disposition; objectId; newObjectId; methodName; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any methodArguments; source; } class ItemsRange { startPosition; endPosition; totalLength; items; constructor(startPosition, endPosition, totalLength, items) { this.startPosition = startPosition; this.endPosition = endPosition; this.totalLength = totalLength; this.items = items; } } class StaticData { nodeCount; rootNodeIndex; totalSize; maxJSObjectId; constructor(nodeCount, rootNodeIndex, totalSize, maxJSObjectId) { this.nodeCount = nodeCount; this.rootNodeIndex = rootNodeIndex; this.totalSize = totalSize; this.maxJSObjectId = maxJSObjectId; } } class NodeFilter { minNodeId; maxNodeId; allocationNodeId; filterName; constructor(minNodeId, maxNodeId) { this.minNodeId = minNodeId; this.maxNodeId = maxNodeId; } equals(o) { return this.minNodeId === o.minNodeId && this.maxNodeId === o.maxNodeId && this.allocationNodeId === o.allocationNodeId && this.filterName === o.filterName; } } class SearchConfig { query; caseSensitive; isRegex; shouldJump; jumpBackward; constructor(query, caseSensitive, isRegex, shouldJump, jumpBackward) { this.query = query; this.caseSensitive = caseSensitive; this.isRegex = isRegex; this.shouldJump = shouldJump; this.jumpBackward = jumpBackward; } toSearchRegex(_global) { throw new Error('Unsupported operation on search config'); } } class Samples { timestamps; lastAssignedIds; sizes; constructor(timestamps, lastAssignedIds, sizes) { this.timestamps = timestamps; this.lastAssignedIds = lastAssignedIds; this.sizes = sizes; } } class Location { scriptId; lineNumber; columnNumber; constructor(scriptId, lineNumber, columnNumber) { this.scriptId = scriptId; this.lineNumber = lineNumber; this.columnNumber = columnNumber; } } var HeapSnapshotModel = /*#__PURE__*/Object.freeze({ __proto__: null, Aggregate: Aggregate, AggregateForDiff: AggregateForDiff, AllocationNodeCallers: AllocationNodeCallers, AllocationStackFrame: AllocationStackFrame, ComparatorConfig: ComparatorConfig, Diff: Diff, DiffForClass: DiffForClass, Edge: Edge, HeapSnapshotProgressEvent: HeapSnapshotProgressEvent, ItemsRange: ItemsRange, Location: Location, Node: Node, NodeFilter: NodeFilter, Samples: Samples, SearchConfig: SearchConfig, SerializedAllocationNode: SerializedAllocationNode, StaticData: StaticData, WorkerCommand: WorkerCommand, baseSystemDistance: baseSystemDistance, baseUnreachableDistance: baseUnreachableDistance }); /* * Copyright (C) 2013 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class AllocationProfile { #strings; #nextNodeId; #functionInfos; #idToNode; #idToTopDownNode; #collapsedTopNodeIdToFunctionInfo; #traceTops; constructor(profile, liveObjectStats) { this.#strings = profile.strings; this.#nextNodeId = 1; this.#functionInfos = []; this.#idToNode = {}; this.#idToTopDownNode = {}; this.#collapsedTopNodeIdToFunctionInfo = {}; this.#traceTops = null; this.#buildFunctionAllocationInfos(profile); this.#buildAllocationTree(profile, liveObjectStats); } #buildFunctionAllocationInfos(profile) { const strings = this.#strings; const functionInfoFields = profile.snapshot.meta.trace_function_info_fields; const functionNameOffset = functionInfoFields.indexOf('name'); const scriptNameOffset = functionInfoFields.indexOf('script_name'); const scriptIdOffset = functionInfoFields.indexOf('script_id'); const lineOffset = functionInfoFields.indexOf('line'); const columnOffset = functionInfoFields.indexOf('column'); const functionInfoFieldCount = functionInfoFields.length; const rawInfos = profile.trace_function_infos; const infoLength = rawInfos.length; const functionInfos = this.#functionInfos = new Array(infoLength / functionInfoFieldCount); let index = 0; for (let i = 0; i < infoLength; i += functionInfoFieldCount) { functionInfos[index++] = new FunctionAllocationInfo(strings[rawInfos[i + functionNameOffset]], strings[rawInfos[i + scriptNameOffset]], rawInfos[i + scriptIdOffset], rawInfos[i + lineOffset], rawInfos[i + columnOffset]); } } #buildAllocationTree(profile, liveObjectStats) { const traceTreeRaw = profile.trace_tree; const functionInfos = this.#functionInfos; const idToTopDownNode = this.#idToTopDownNode; const traceNodeFields = profile.snapshot.meta.trace_node_fields; const nodeIdOffset = traceNodeFields.indexOf('id'); const functionInfoIndexOffset = traceNodeFields.indexOf('function_info_index'); const allocationCountOffset = traceNodeFields.indexOf('count'); const allocationSizeOffset = traceNodeFields.indexOf('size'); const childrenOffset = traceNodeFields.indexOf('children'); const nodeFieldCount = traceNodeFields.length; function traverseNode( // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration) // eslint-disable-next-line @typescript-eslint/no-explicit-any rawNodeArray, nodeOffset, parent) { const functionInfo = functionInfos[rawNodeArray[nodeOffset + functionInfoIndexOffset]]; const id = rawNodeArray[nodeOffset + nodeIdOffset]; const stats = liveObjectStats[id]; const liveCount = stats ? stats.count : 0; const liveSize = stats ? stats.size : 0; const result = new TopDownAllocationNode(id, functionInfo, rawNodeArray[nodeOffset + allocationCountOffset], rawNodeArray[nodeOffset + allocationSizeOffset], liveCount, liveSize, parent); idToTopDownNode[id] = result; functionInfo.addTraceTopNode(result); const rawChildren = rawNodeArray[nodeOffset + childrenOffset]; for (let i = 0; i < rawChildren.length; i += nodeFieldCount) { result.children.push(traverseNode(rawChildren, i, result)); } return result; } return traverseNode(traceTreeRaw, 0, null); } serializeTraceTops() { if (this.#traceTops) { return this.#traceTops; } const result = this.#traceTops = []; const functionInfos = this.#functionInfos; for (let i = 0; i < functionInfos.length; i++) { const info = functionInfos[i]; if (info.totalCount === 0) { continue; } const nodeId = this.#nextNodeId++; const isRoot = i === 0; result.push(this.#serializeNode(nodeId, info, info.totalCount, info.totalSize, info.totalLiveCount, info.totalLiveSize, !isRoot)); this.#collapsedTopNodeIdToFunctionInfo[nodeId] = info; } result.sort(function (a, b) { return b.size - a.size; }); return result; } serializeCallers(nodeId) { let node = this.#ensureBottomUpNode(nodeId); const nodesWithSingleCaller = []; while (node.callers().length === 1) { node = node.callers()[0]; nodesWithSingleCaller.push(this.#serializeCaller(node)); } const branchingCallers = []; const callers = node.callers(); for (let i = 0; i < callers.length; i++) { branchingCallers.push(this.#serializeCaller(callers[i])); } return new AllocationNodeCallers(nodesWithSingleCaller, branchingCallers); } serializeAllocationStack(traceNodeId) { let node = this.#idToTopDownNode[traceNodeId]; const result = []; while (node) { const functionInfo = node.functionInfo; result.push(new AllocationStackFrame(functionInfo.functionName, functionInfo.scriptName, functionInfo.scriptId, functionInfo.line, functionInfo.column)); node = node.parent; } return result; } traceIds(allocationNodeId) { return this.#ensureBottomUpNode(allocationNodeId).traceTopIds; } #ensureBottomUpNode(nodeId) { let node = this.#idToNode[nodeId]; if (!node) { const functionInfo = this.#collapsedTopNodeIdToFunctionInfo[nodeId]; node = functionInfo.bottomUpRoot(); delete this.#collapsedTopNodeIdToFunctionInfo[nodeId]; this.#idToNode[nodeId] = node; } return node; } #serializeCaller(node) { const callerId = this.#nextNodeId++; this.#idToNode[callerId] = node; return this.#serializeNode(callerId, node.functionInfo, node.allocationCount, node.allocationSize, node.liveCount, node.liveSize, node.hasCallers()); } #serializeNode(nodeId, functionInfo, count, size, liveCount, liveSize, hasChildren) { return new SerializedAllocationNode(nodeId, functionInfo.functionName, functionInfo.scriptName, functionInfo.scriptId, functionInfo.line, functionInfo.column, count, size, liveCount, liveSize, hasChildren); } } class TopDownAllocationNode { id; functionInfo; allocationCount; allocationSize; liveCount; liveSize; parent; children; constructor(id, functionInfo, count, size, liveCount, liveSize, parent) { this.id = id; this.functionInfo = functionInfo; this.allocationCount = count; this.allocationSize = size; this.liveCount = liveCount; this.liveSize = liveSize; this.parent = parent; this.children = []; } } class BottomUpAllocationNode { functionInfo; allocationCount; allocationSize; liveCount; liveSize; traceTopIds; #callersInternal; constructor(functionInfo) { this.functionInfo = functionInfo; this.allocationCount = 0; this.allocationSize = 0; this.liveCount = 0; this.liveSize = 0; this.traceTopIds = []; this.#callersInternal = []; } addCaller(traceNode) { const functionInfo = traceNode.functionInfo; let result; for (let i = 0; i < this.#callersInternal.length; i++) { const caller = this.#callersInternal[i]; if (caller.functionInfo === functionInfo) { result = caller; break; } } if (!result) { result = new BottomUpAllocationNode(functionInfo); this.#callersInternal.push(result); } return result; } callers() { return this.#callersInternal; } hasCallers() { return this.#callersInternal.length > 0; } } class FunctionAllocationInfo { functionName; scriptName; scriptId; line; column; totalCount; totalSize; totalLiveCount; totalLiveSize; #traceTops; #bottomUpTree; constructor(functionName, scriptName, scriptId, line, column) { this.functionName = functionName; this.scriptName = scriptName; this.scriptId = scriptId; this.line = line; this.column = column; this.totalCount = 0; this.totalSize = 0; this.totalLiveCount = 0; this.totalLiveSize = 0; this.#traceTops = []; } addTraceTopNode(node) { if (node.allocationCount === 0) { return; } this.#traceTops.push(node); this.totalCount += node.allocationCount; this.totalSize += node.allocationSize; this.totalLiveCount += node.liveCount; this.totalLiveSize += node.liveSize; } bottomUpRoot() { if (!this.#traceTops.length) { return null; } if (!this.#bottomUpTree) { this.#buildAllocationTraceTree(); } return this.#bottomUpTree; } #buildAllocationTraceTree() { this.#bottomUpTree = new BottomUpAllocationNode(this); for (let i = 0; i < this.#traceTops.length; i++) { let node = this.#traceTops[i]; let bottomUpNode = this.#bottomUpTree; const count = node.allocationCount; const size = node.allocationSize; const liveCount = node.liveCount; const liveSize = node.liveSize; const traceId = node.id; while (true) { bottomUpNode.allocationCount += count; bottomUpNode.allocationSize += size; bottomUpNode.liveCount += liveCount; bottomUpNode.liveSize += liveSize; bottomUpNode.traceTopIds.push(traceId); node = node.parent; if (node === null) { break; } bottomUpNode = bottomUpNode.addCaller(node); } } } } const withResolvers = () => { let resolve; let reject; const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return { resolve, reject, promise } }; const toSorted = (arr) => { const res = [...arr]; res.sort(); return res }; // Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. let devToolsLocaleInstance = null; /** * Simple class that determines the DevTools locale based on: * 1) navigator.language, which matches the Chrome UI * 2) the value of the "language" Setting the user choses * 3) available locales in DevTools. * * The DevTools locale is only determined once during startup and * guaranteed to never change. Use this class when using * `Intl` APIs. */ class DevToolsLocale { locale; lookupClosestDevToolsLocale; constructor(data) { this.lookupClosestDevToolsLocale = data.lookupClosestDevToolsLocale; // TODO(crbug.com/1163928): Use constant once setting actually exists. if (data.settingLanguage === 'browserLanguage') { this.locale = data.navigatorLanguage || 'en-US'; } else { this.locale = data.settingLanguage; } this.locale = this.lookupClosestDevToolsLocale(this.locale); } static instance(opts = { create: false }) { if (!devToolsLocaleInstance && !opts.create) { throw new Error('No LanguageSelector instance exists yet.'); } if (opts.create) { devToolsLocaleInstance = new DevToolsLocale(opts.data); } return devToolsLocaleInstance; } static removeInstance() { devToolsLocaleInstance = null; } forceFallbackLocale() { // Locale is 'readonly', this is the only case where we want to forcibly // overwrite the locale. this.locale = 'en-US'; } /** * Returns true iff DevTools supports the language of the passed locale. * Note that it doesn't have to be a one-to-one match, e.g. if DevTools supports * 'de', then passing 'de-AT' will return true. */ languageIsSupportedByDevTools(localeString) { return localeLanguagesMatch(localeString, this.lookupClosestDevToolsLocale(localeString)); } } /** * Returns true iff the two locales have matching languages. This means the * passing 'de-AT' and 'de-DE' will return true, while 'de-DE' and 'en' will * return false. */ function localeLanguagesMatch(localeString1, localeString2) { const locale1 = new Intl.Locale(localeString1); const locale2 = new Intl.Locale(localeString2); return locale1.language === locale2.language; } function IntlMessageFormat () { throw new Error("not implemented"); } // Copyright 2018 The Lighthouse Authors. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. const EMPTY_VALUES_OBJECT = {}; /** * This class is usually created at module instantiation time and * holds the filename, the UIStrings object and a reference to * all the localization data. * * Later, once needed, users can request a `LocalizedStringSet` that represents * all the translated strings, in a given locale for the specific file and * UIStrings object. * * Please note that this class is implemented with invariant in mind that the * DevTools locale never changes. Otherwise we would have to use a Map as * the cache. For performance reasons, we store the single possible map entry * as a property directly. * * The DevTools locale CANNOT be passed via the constructor. When instances * of `RegisteredFileStrings` are created, the DevTools locale has not yet * been determined. */ class RegisteredFileStrings { filename; stringStructure; localizedMessages; localizedStringSet; constructor(filename, stringStructure, localizedMessages) { this.filename = filename; this.stringStructure = stringStructure; this.localizedMessages = localizedMessages; } getLocalizedStringSetFor(locale) { if (this.localizedStringSet) { return this.localizedStringSet; } const localeData = this.localizedMessages.get(locale); if (!localeData) { throw new Error(`No locale data registered for '${locale}'`); } this.localizedStringSet = new LocalizedStringSet(this.filename, this.stringStructure, locale, localeData); return this.localizedStringSet; } } /** * A set of translated strings for a single file in a specific locale. * * The class is a wrapper around `IntlMessageFormat#format` plus a cache * to speed up consecutive lookups of the same message. */ class LocalizedStringSet { filename; stringStructure; localizedMessages; cachedSimpleStrings = new Map(); cachedMessageFormatters = new Map(); /** For pseudo locales, use 'de-DE' for number formatting */ localeForFormatter; constructor(filename, stringStructure, locale, localizedMessages) { this.filename = filename; this.stringStructure = stringStructure; this.localizedMessages = localizedMessages; this.localeForFormatter = (locale === 'en-XA' || locale === 'en-XL') ? 'de-DE' : locale; } getLocalizedString(message, values = EMPTY_VALUES_OBJECT) { if (values === EMPTY_VALUES_OBJECT || Object.keys(values).length === 0) { return this.getSimpleLocalizedString(message); } return this.getFormattedLocalizedString(message, values); } getMessageFormatterFor(message) { const keyname = Object.keys(this.stringStructure).find(key => this.stringStructure[key] === message); if (!keyname) { throw new Error(`Unable to locate '${message}' in UIStrings object`); } const i18nId = `${this.filename} | ${keyname}`; const localeMessage = this.localizedMessages[i18nId]; // The requested string might not yet have been collected into en-US.json or // been translated yet. Fall back to the original TypeScript UIStrings message. const messageToTranslate = localeMessage ? localeMessage.message : message; return new IntlMessageFormat(messageToTranslate, this.localeForFormatter); } getSimpleLocalizedString(message) { const cachedSimpleString = this.cachedSimpleStrings.get(message); if (cachedSimpleString) { return cachedSimpleString; } const formatter = this.getMessageFormatterFor(message); try { const translatedString = formatter.format(); this.cachedSimpleStrings.set(message, translatedString); return translatedString; } catch { // The message could have been updated and use different placeholders then // the translation. This is a rare edge case so it's fine to create a temporary // IntlMessageFormat and fall back to the UIStrings message. const formatter = new IntlMessageFormat(message, this.localeForFormatter); const translatedString = formatter.format(); this.cachedSimpleStrings.set(message, translatedString); return translatedString; } } getFormattedLocalizedString(message, values) { let formatter = this.cachedMessageFormatters.get(message); if (!formatter) { formatter = this.getMessageFormatterFor(message); this.cachedMessageFormatters.set(message, formatter); } try { return formatter.format(values); } catch { // The message could have been updated and use different placeholders then // the translation. This is a rare edge case so it's fine to create a temporary // IntlMessageFormat and fall back to the UIStrings message. const formatter = new IntlMessageFormat(message, this.localeForFormatter); return formatter.format(values); } } } // Copyright 2018 The Lighthouse Authors. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. /** * Encapsulates the global state of the i18n runtime. */ class I18n { supportedLocales; localeData = new Map(); defaultLocale; constructor(supportedLocales, defaultLocale) { this.defaultLocale = defaultLocale; this.supportedLocales = new Set(supportedLocales); } registerLocaleData(locale, messages) { this.localeData.set(locale, messages); } hasLocaleDataForTest(locale) { return this.localeData.has(locale); } resetLocaleDataForTest() { this.localeData.clear(); } registerFileStrings(filename, stringStructure) { return new RegisteredFileStrings(filename, stringStructure, this.localeData); } /** * Look up the best available locale for the requested language through these fall backs: * - exact match * - progressively shorter prefixes (`de-CH-1996` -> `de-CH` -> `de`) * - the default locale if no match is found */ lookupClosestSupportedLocale(locale) { const canonicalLocale = Intl.getCanonicalLocales(locale)[0]; const localeParts = canonicalLocale.split('-'); while (localeParts.length) { const candidate = localeParts.join('-'); if (this.supportedLocales.has(candidate)) { return candidate; } localeParts.pop(); } return this.defaultLocale; } } // Copyright (c) 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. function swap(array, i1, i2) { const temp = array[i1]; array[i1] = array[i2]; array[i2] = temp; } function partition(array, comparator, left, right, pivotIndex) { const pivotValue = array[pivotIndex]; swap(array, right, pivotIndex); let storeIndex = left; for (let i = left; i < right; ++i) { if (comparator(array[i], pivotValue) < 0) { swap(array, storeIndex, i); ++storeIndex; } } swap(array, right, storeIndex); return storeIndex; } function quickSortRange(array, comparator, left, right, sortWindowLeft, sortWindowRight) { if (right <= left) { return; } const pivotIndex = Math.floor(Math.random() * (right - left)) + left; const pivotNewIndex = partition(array, comparator, left, right, pivotIndex); if (sortWindowLeft < pivotNewIndex) { quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowLeft, sortWindowRight); } if (pivotNewIndex < sortWindowRight) { quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindowLeft, sortWindowRight); } } function sortRange(array, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight) { if (leftBound === 0 && rightBound === (array.length - 1) && sortWindowLeft === 0 && sortWindowRight >= rightBound) { array.sort(comparator); } else { quickSortRange(array, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight); } return array; } const DEFAULT_COMPARATOR = (a, b) => { return a < b ? -1 : (a > b ? 1 : 0); }; function lowerBound(array, needle, comparator, left, right) { let l = 0; let r = right !== undefined ? right : array.length; while (l < r) { const m = (l + r) >> 1; if (comparator(needle, array[m]) > 0) { l = m + 1; } else { r = m; } } return r; } // Copyright (c) 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. class Multimap { map = new Map(); set(key, value) { let set = this.map.get(key); if (!set) { set = new Set(); this.map.set(key, set); } set.add(value); } get(key) { return this.map.get(key) || new Set(); } has(key) { return this.map.has(key); } hasValue(key, value) { const set = this.map.get(key); if (!set) { return false; } return set.has(value); } get size() { return this.map.size; } delete(key, value) { const values = this.get(key); if (!values) { return false; } const result = values.delete(value); if (!values.size) { this.map.delete(key); } return result; } deleteAll(key) { this.map.delete(key); } keysArray() { return [...this.map.keys()]; } keys() { return this.map.keys(); } valuesArray() { const result = []; for (const set of this.map.values()) { result.push(...set.values()); } return result; } clear() { this.map.clear(); } } // Copyright (c) 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. const SPECIAL_REGEX_CHARACTERS = '^[]{}()\\.^$*+?|-,'; const regexSpecialCharacters = function () { return SPECIAL_REGEX_CHARACTERS; }; const createPlainTextSearchRegex = function (query, flags) { // This should be kept the same as the one in StringUtil.cpp. let regex = ''; for (let i = 0; i < query.length; ++i) { const c = query.charAt(i); if (regexSpecialCharacters().indexOf(c) !== -1) { regex += '\\'; } regex += c; } return new RegExp(regex, flags); }; // Copyright 2024 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @returns A BigUint32Array implementation which is based on Array. * This means that its length automatically expands to include the highest index * used, and asArrayOrFail will succeed. */ function createExpandableBigUint32Array() { return new ExpandableBigUint32ArrayImpl(); } /** * @returns A BigUint32Array implementation which is based on Uint32Array. * If the length is small enough to fit in a single Uint32Array, then * asUint32ArrayOrFail will succeed. Otherwise, it will throw an exception. */ function createFixedBigUint32Array(length, maxLengthForTesting) { try { if (maxLengthForTesting !== undefined && length > maxLengthForTesting) ; return new BasicBigUint32ArrayImpl(length); } catch { // We couldn't allocate a big enough ArrayBuffer. return new SplitBigUint32ArrayImpl(length, maxLengthForTesting); } } class BasicBigUint32ArrayImpl extends Uint32Array { getValue(index) { return this[index]; } setValue(index, value) { this[index] = value; } asUint32ArrayOrFail() { return this; } asArrayOrFail() { throw new Error('Not an array'); } } class SplitBigUint32ArrayImpl { #data; #partLength; length; constructor(length, maxLengthForTesting) { this.#data = []; this.length = length; let partCount = 1; while (true) { partCount *= 2; this.#partLength = Math.ceil(length / partCount); try { if (maxLengthForTesting !== undefined && this.#partLength > maxLengthForTesting) { // Simulate allocation failure. throw new RangeError(); } for (let i = 0; i < partCount; ++i) { this.#data[i] = new Uint32Array(this.#partLength); } return; } catch (e) { if (this.#partLength < 1e6) { // The length per part is already small, so continuing to subdivide it // will probably not help. throw e; } } } } getValue(index) { if (index >= 0 && index < this.length) { const partLength = this.#partLength; return this.#data[Math.floor(index / partLength)][index % partLength]; } // On out-of-bounds accesses, match the behavior of Uint32Array: return an // undefined value that's incorrectly typed as number. return this.#data[0][-1]; } setValue(index, value) { if (index >= 0 && index < this.length) { const partLength = this.#partLength; this.#data[Math.floor(index / partLength)][index % partLength] = value; } // Attempting to set a value out of bounds does nothing, like Uint32Array. } asUint32ArrayOrFail() { throw new Error('Not a Uint32Array'); } asArrayOrFail() { throw new Error('Not an array'); } } class ExpandableBigUint32ArrayImpl extends Array { getValue(index) { return this[index]; } setValue(index, value) { this[index] = value; } asUint32ArrayOrFail() { throw new Error('Not a Uint32Array'); } asArrayOrFail() { return this; } } function createBitVector(lengthOrBuffer) { return new BitVectorImpl(lengthOrBuffer); } class BitVectorImpl extends Uint8Array { constructor(lengthOrBuffer) { if (typeof lengthOrBuffer === 'number') { super(Math.ceil(lengthOrBuffer / 8)); } else { super(lengthOrBuffer); } } getBit(index) { const value = this[index >> 3] & (1 << (index & 7)); return value !== 0; } setBit(index) { this[index >> 3] |= (1 << (index & 7)); } clearBit(index) { this[index >> 3] &= ~(1 << (index & 7)); } previous(index) { // First, check for more bits in the current byte. while (index !== ((index >> 3) << 3)) { --index; if (this.getBit(index)) { return index; } } // Next, iterate by bytes to skip over ranges of zeros. let byteIndex = (index >> 3) - 1; while (byteIndex >= 0 && this[byteIndex] === 0) { --byteIndex; } if (byteIndex < 0) { return -1; } // Finally, iterate the nonzero byte to find the highest bit. for (index = (byteIndex << 3) + 7; index >= (byteIndex << 3); --index) { if (this.getBit(index)) { return index; } } throw new Error('Unreachable'); } } // Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. new URLSearchParams(); var GenAiEnterprisePolicyValue; (function (GenAiEnterprisePolicyValue) { GenAiEnterprisePolicyValue[GenAiEnterprisePolicyValue["ALLOW"] = 0] = "ALLOW"; GenAiEnterprisePolicyValue[GenAiEnterprisePolicyValue["ALLOW_WITHOUT_LOGGING"] = 1] = "ALLOW_WITHOUT_LOGGING"; GenAiEnterprisePolicyValue[GenAiEnterprisePolicyValue["DISABLE"] = 2] = "DISABLE"; })(GenAiEnterprisePolicyValue || (GenAiEnterprisePolicyValue = {})); var HostConfigFreestylerExecutionMode; (function (HostConfigFreestylerExecutionMode) { HostConfigFreestylerExecutionMode["ALL_SCRIPTS"] = "ALL_SCRIPTS"; HostConfigFreestylerExecutionMode["SIDE_EFFECT_FREE_SCRIPTS_ONLY"] = "SIDE_EFFECT_FREE_SCRIPTS_ONLY"; HostConfigFreestylerExecutionMode["NO_SCRIPTS"] = "NO_SCRIPTS"; })(HostConfigFreestylerExecutionMode || (HostConfigFreestylerExecutionMode = {})); const LOCALES = ['en-GB']; const DEFAULT_LOCALE = 'en-GB'; // Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable rulesdir/no-imperative-dom-api */ const i18nInstance = new I18n(LOCALES, DEFAULT_LOCALE); /** * Returns an anonymous function that wraps a call to retrieve a localized string. * This is introduced so that localized strings can be declared in environments where * the i18n system has not been configured and so, cannot be directly invoked. Instead, * strings are lazily localized when they are used. This is used for instance in the * meta files used to register module extensions. */ function getLazilyComputedLocalizedString(registeredStrings, id, values = {}) { return () => getLocalizedString(registeredStrings, id, values); } /** * Retrieve the localized string. */ function getLocalizedString(registeredStrings, id, values = {}) { return registeredStrings.getLocalizedStringSetFor(DevToolsLocale.instance().locale).getLocalizedString(id, values); } /** * Register a file's UIStrings with i18n, return function to generate the string ids. */ function registerUIStrings(path, stringStructure) { return i18nInstance.registerFileStrings(path, stringStructure); } function serializeUIString(string, values = {}) { const serializedMessage = { string, values }; return JSON.stringify(serializedMessage); } class HeapSnapshotEdge { snapshot; edges; edgeIndex; constructor(snapshot, edgeIndex) { this.snapshot = snapshot; this.edges = snapshot.containmentEdges; this.edgeIndex = edgeIndex || 0; } clone() { return new HeapSnapshotEdge(this.snapshot, this.edgeIndex); } hasStringName() { throw new Error('Not implemented'); } name() { throw new Error('Not implemented'); } node() { return this.snapshot.createNode(this.nodeIndex()); } nodeIndex() { if (typeof this.snapshot.edgeToNodeOffset === 'undefined') { throw new Error('edgeToNodeOffset is undefined'); } return this.edges.getValue(this.edgeIndex + this.snapshot.edgeToNodeOffset); } toString() { return 'HeapSnapshotEdge: ' + this.name(); } type() { return this.snapshot.edgeTypes[this.rawType()]; } itemIndex() { return this.edgeIndex; } serialize() { return new Edge(this.name(), this.node().serialize(), this.type(), this.edgeIndex); } rawType() { if (typeof this.snapshot.edgeTypeOffset === 'undefined') { throw new Error('edgeTypeOffset is undefined'); } return this.edges.getValue(this.edgeIndex + this.snapshot.edgeTypeOffset); } isInternal() { throw new Error('Not implemented'); } isInvisible() { throw new Error('Not implemented'); } isWeak() { throw new Error('Not implemented'); } getValueForSorting(_fieldName) { throw new Error('Not implemented'); } nameIndex() { throw new Error('Not implemented'); } } class HeapSnapshotNodeIndexProvider { #node; constructor(snapshot) { this.#node = snapshot.createNode(); } itemForIndex(index) { this.#node.nodeIndex = index; return this.#node; } } class HeapSnapshotEdgeIndexProvider { #edge; constructor(snapshot) { this.#edge = snapshot.createEdge(0); } itemForIndex(index) { this.#edge.edgeIndex = index; return this.#edge; } } class HeapSnapshotRetainerEdgeIndexProvider { #retainerEdge; constructor(snapshot) { this.#retainerEdge = snapshot.createRetainingEdge(0); } itemForIndex(index) { this.#retainerEdge.setRetainerIndex(index); return this.#retainerEdge; } } class HeapSnapshotEdgeIterator { #sourceNode; edge; constructor(node) { this.#sourceNode = node; this.edge = node.snapshot.createEdge(node.edgeIndexesStart()); } hasNext() { return this.edge.edgeIndex < this.#sourceNode.edgeIndexesEnd(); } item() { return this.edge; } next() { if (typeof this.edge.snapshot.edgeFieldsCount === 'undefined') { throw new Error('edgeFieldsCount is undefined'); } this.edge.edgeIndex += this.edge.snapshot.edgeFieldsCount; } } class HeapSnapshotRetainerEdge { snapshot; #retainerIndexInternal; #globalEdgeIndex; #retainingNodeIndex; #edgeInstance; #nodeInstance; constructor(snapshot, retainerIndex) { this.snapshot = snapshot; this.setRetainerIndex(retainerIndex); } clone() { return new HeapSnapshotRetainerEdge(this.snapshot, this.retainerIndex()); } hasStringName() { return this.edge().hasStringName(); } name() { return this.edge().name(); } nameIndex() { return this.edge().nameIndex(); } node() { return this.nodeInternal(); } nodeIndex() { if (typeof this.#retainingNodeIndex === 'undefined') { throw new Error('retainingNodeIndex is undefined'); } return this.#retainingNodeIndex; } retainerIndex() { return this.#retainerIndexInternal; } setRetainerIndex(retainerIndex) { if (retainerIndex === this.#retainerIndexInternal) { return; } if (!this.snapshot.retainingEdges || !this.snapshot.retainingNodes) { throw new Error('Snapshot does not contain retaining edges or retaining nodes'); } this.#retainerIndexInternal = retainerIndex; this.#globalEdgeIndex = this.snapshot.retainingEdges[retainerIndex]; this.#retainingNodeIndex = this.snapshot.retainingNodes[retainerIndex]; this.#edgeInstance = null; this.#nodeInstance = null; } set edgeIndex(edgeIndex) { this.setRetainerIndex(edgeIndex); } nodeInternal() { if (!this.#nodeInstance) { this.#nodeInstance = this.snapshot.createNode(this.#retainingNodeIndex); } return this.#nodeInstance; } edge() { if (!this.#edgeInstance) { this.#edgeInstance = this.snapshot.createEdge(this.#globalEdgeIndex); } return this.#edgeInstance; } toString() { return this.edge().toString(); } itemIndex() { return this.#retainerIndexInternal; } serialize() { const node = this.node(); const serializedNode = node.serialize(); serializedNode.distance = this.#distance(); serializedNode.ignored = this.snapshot.isNodeIgnoredInRetainersView(node.nodeIndex); return new Edge(this.name(), serializedNode, this.type(), this.#globalEdgeIndex); } type() { return this.edge().type(); } isInternal() { return this.edge().isInternal(); } getValueForSorting(fieldName) { if (fieldName === '!edgeDistance') { return this.#distance(); } throw new Error('Invalid field name'); } #distance() { if (this.snapshot.isEdgeIgnoredInRetainersView(this.#globalEdgeIndex)) { return baseUnreachableDistance; } return this.node().distanceForRetainersView(); } } class HeapSnapshotRetainerEdgeIterator { #retainersEnd; retainer; constructor(retainedNode) { const snapshot = retainedNode.snapshot; const retainedNodeOrdinal = retainedNode.ordinal(); if (!snapshot.firstRetainerIndex) { throw new Error('Snapshot does not contain firstRetainerIndex'); } const retainerIndex = snapshot.firstRetainerIndex[retainedNodeOrdinal]; this.#retainersEnd = snapshot.firstRetainerIndex[retainedNodeOrdinal + 1]; this.retainer = snapshot.createRetainingEdge(retainerIndex); } hasNext() { return this.retainer.retainerIndex() < this.#retainersEnd; } item() { return this.retainer; } next() { this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1); } } class HeapSnapshotNode { snapshot; nodeIndex; constructor(snapshot, nodeIndex) { this.snapshot = snapshot; this.nodeIndex = nodeIndex || 0; } distance() { return this.snapshot.nodeDistances[this.nodeIndex / this.snapshot.nodeFieldCount]; } distanceForRetainersView() { return this.snapshot.getDistanceForRetainersView(this.nodeIndex); } className() { return this.snapshot.strings[this.classIndex()]; } classIndex() { return this.#detachednessAndClassIndex() >>> SHIFT_FOR_CLASS_INDEX; } // Returns a key which can uniquely describe both the class name for this node // and its Location, if relevant. These keys are meant to be cheap to produce, // so that building aggregates is fast. These keys are NOT the same as the // keys exposed to the frontend by functions such as aggregatesWithFilter and